All Files ( 87.23% covered at 14140.31 hits/line )
322 files in total.
19712 relevant lines,
17194 lines covered and
2518 lines missed.
(
87.23%
)
-
# frozen_string_literal: true
-
-
#--
-
# Copyright (c) 2004-2020 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
3
require "active_support"
-
3
require "active_support/rails"
-
3
require "active_model"
-
3
require "arel"
-
3
require "yaml"
-
-
3
require "active_record/version"
-
3
require "active_model/attribute_set"
-
3
require "active_record/errors"
-
-
3
module ActiveRecord
-
3
extend ActiveSupport::Autoload
-
-
3
autoload :Base
-
3
autoload :Callbacks
-
3
autoload :Core
-
3
autoload :ConnectionHandling
-
3
autoload :CounterCache
-
3
autoload :DynamicMatchers
-
3
autoload :DelegatedType
-
3
autoload :Enum
-
3
autoload :InternalMetadata
-
3
autoload :Explain
-
3
autoload :Inheritance
-
3
autoload :Integration
-
3
autoload :Migration
-
3
autoload :Migrator, "active_record/migration"
-
3
autoload :ModelSchema
-
3
autoload :NestedAttributes
-
3
autoload :NoTouching
-
3
autoload :TouchLater
-
3
autoload :Persistence
-
3
autoload :QueryCache
-
3
autoload :Querying
-
3
autoload :ReadonlyAttributes
-
3
autoload :RecordInvalid, "active_record/validations"
-
3
autoload :Reflection
-
3
autoload :RuntimeRegistry
-
3
autoload :Sanitization
-
3
autoload :Schema
-
3
autoload :SchemaDumper
-
3
autoload :SchemaMigration
-
3
autoload :Scoping
-
3
autoload :Serialization
-
3
autoload :StatementCache
-
3
autoload :Store
-
3
autoload :SignedId
-
3
autoload :Suppressor
-
3
autoload :Timestamp
-
3
autoload :Transactions
-
3
autoload :Translation
-
3
autoload :Validations
-
3
autoload :SecureToken
-
-
3
eager_autoload do
-
3
autoload :ConnectionAdapters
-
-
3
autoload :Aggregations
-
3
autoload :Associations
-
3
autoload :AttributeAssignment
-
3
autoload :AttributeMethods
-
3
autoload :AutosaveAssociation
-
-
3
autoload :LegacyYamlAdapter
-
-
3
autoload :Relation
-
3
autoload :AssociationRelation
-
3
autoload :NullRelation
-
-
3
autoload_under "relation" do
-
3
autoload :QueryMethods
-
3
autoload :FinderMethods
-
3
autoload :Calculations
-
3
autoload :PredicateBuilder
-
3
autoload :SpawnMethods
-
3
autoload :Batches
-
3
autoload :Delegation
-
end
-
-
3
autoload :Result
-
3
autoload :TableMetadata
-
3
autoload :Type
-
end
-
-
3
module Coders
-
3
autoload :YAMLColumn, "active_record/coders/yaml_column"
-
3
autoload :JSON, "active_record/coders/json"
-
end
-
-
3
module AttributeMethods
-
3
extend ActiveSupport::Autoload
-
-
3
eager_autoload do
-
3
autoload :BeforeTypeCast
-
3
autoload :Dirty
-
3
autoload :PrimaryKey
-
3
autoload :Query
-
3
autoload :Read
-
3
autoload :TimeZoneConversion
-
3
autoload :Write
-
3
autoload :Serialization
-
end
-
end
-
-
3
module Locking
-
3
extend ActiveSupport::Autoload
-
-
3
eager_autoload do
-
3
autoload :Optimistic
-
3
autoload :Pessimistic
-
end
-
end
-
-
3
module Scoping
-
3
extend ActiveSupport::Autoload
-
-
3
eager_autoload do
-
3
autoload :Named
-
3
autoload :Default
-
end
-
end
-
-
3
module Middleware
-
3
extend ActiveSupport::Autoload
-
-
3
autoload :DatabaseSelector, "active_record/middleware/database_selector"
-
end
-
-
3
module Tasks
-
3
extend ActiveSupport::Autoload
-
-
3
autoload :DatabaseTasks
-
3
autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
-
3
autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
-
3
autoload :PostgreSQLDatabaseTasks,
-
"active_record/tasks/postgresql_database_tasks"
-
end
-
-
3
autoload :TestDatabases, "active_record/test_databases"
-
3
autoload :TestFixtures, "active_record/fixtures"
-
-
3
def self.eager_load!
-
super
-
ActiveRecord::Locking.eager_load!
-
ActiveRecord::Scoping.eager_load!
-
ActiveRecord::Associations.eager_load!
-
ActiveRecord::AttributeMethods.eager_load!
-
ActiveRecord::ConnectionAdapters.eager_load!
-
end
-
end
-
-
3
ActiveSupport.on_load(:active_record) do
-
3
Arel::Table.engine = self
-
end
-
-
3
ActiveSupport.on_load(:i18n) do
-
3
I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
-
end
-
-
3
YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
-
3
YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
-
3
YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"
-
3
YAML.load_tags["!ruby/object:ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString"] = "ActiveRecord::Type::String"
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# See ActiveRecord::Aggregations::ClassMethods for documentation
-
3
module Aggregations
-
3
def initialize_dup(*) # :nodoc:
-
3
@aggregation_cache = {}
-
3
super
-
end
-
-
3
def reload(*) # :nodoc:
-
21
clear_aggregation_cache
-
21
super
-
end
-
-
3
private
-
3
def clear_aggregation_cache
-
21
@aggregation_cache.clear if persisted?
-
end
-
-
3
def init_internals
-
267
@aggregation_cache = {}
-
267
super
-
end
-
-
# Active Record implements aggregation through a macro-like class method called #composed_of
-
# for representing attributes as value objects. It expresses relationships like "Account [is]
-
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
-
# to the macro adds a description of how the value objects are created from the attributes of
-
# the entity object (when the entity is initialized either as a new object or from finding an
-
# existing object) and how it can be turned back into attributes (when the entity is saved to
-
# the database).
-
#
-
# class Customer < ActiveRecord::Base
-
# composed_of :balance, class_name: "Money", mapping: %w(balance amount)
-
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
-
# end
-
#
-
# The customer class now has the following methods to manipulate the value objects:
-
# * <tt>Customer#balance, Customer#balance=(money)</tt>
-
# * <tt>Customer#address, Customer#address=(address)</tt>
-
#
-
# These methods will operate with value objects like the ones described below:
-
#
-
# class Money
-
# include Comparable
-
# attr_reader :amount, :currency
-
# EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
-
#
-
# def initialize(amount, currency = "USD")
-
# @amount, @currency = amount, currency
-
# end
-
#
-
# def exchange_to(other_currency)
-
# exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
-
# Money.new(exchanged_amount, other_currency)
-
# end
-
#
-
# def ==(other_money)
-
# amount == other_money.amount && currency == other_money.currency
-
# end
-
#
-
# def <=>(other_money)
-
# if currency == other_money.currency
-
# amount <=> other_money.amount
-
# else
-
# amount <=> other_money.exchange_to(currency).amount
-
# end
-
# end
-
# end
-
#
-
# class Address
-
# attr_reader :street, :city
-
# def initialize(street, city)
-
# @street, @city = street, city
-
# end
-
#
-
# def close_to?(other_address)
-
# city == other_address.city
-
# end
-
#
-
# def ==(other_address)
-
# city == other_address.city && street == other_address.street
-
# end
-
# end
-
#
-
# Now it's possible to access attributes from the database through the value objects instead. If
-
# you choose to name the composition the same as the attribute's name, it will be the only way to
-
# access that attribute. That's the case with our +balance+ attribute. You interact with the value
-
# objects just like you would with any other attribute:
-
#
-
# customer.balance = Money.new(20) # sets the Money value object and the attribute
-
# customer.balance # => Money value object
-
# customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
-
# customer.balance > Money.new(10) # => true
-
# customer.balance == Money.new(20) # => true
-
# customer.balance < Money.new(5) # => false
-
#
-
# Value objects can also be composed of multiple attributes, such as the case of Address. The order
-
# of the mappings will determine the order of the parameters.
-
#
-
# customer.address_street = "Hyancintvej"
-
# customer.address_city = "Copenhagen"
-
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
-
#
-
# customer.address = Address.new("May Street", "Chicago")
-
# customer.address_street # => "May Street"
-
# customer.address_city # => "Chicago"
-
#
-
# == Writing value objects
-
#
-
# Value objects are immutable and interchangeable objects that represent a given value, such as
-
# a Money object representing $5. Two Money objects both representing $5 should be equal (through
-
# methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
-
# unlike entity objects where equality is determined by identity. An entity class such as Customer can
-
# easily have two different objects that both have an address on Hyancintvej. Entity identity is
-
# determined by object or relational unique identifiers (such as primary keys). Normal
-
# ActiveRecord::Base classes are entity objects.
-
#
-
# It's also important to treat the value objects as immutable. Don't allow the Money object to have
-
# its amount changed after creation. Create a new Money object with the new value instead. The
-
# <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
-
# its own values. Active Record won't persist value objects that have been changed through means
-
# other than the writer method.
-
#
-
# The immutable requirement is enforced by Active Record by freezing any object assigned as a value
-
# object. Attempting to change it afterwards will result in a +RuntimeError+.
-
#
-
# Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
-
# keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
-
#
-
# == Custom constructors and converters
-
#
-
# By default value objects are initialized by calling the <tt>new</tt> constructor of the value
-
# class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
-
# option, as arguments. If the value class doesn't support this convention then #composed_of allows
-
# a custom constructor to be specified.
-
#
-
# When a new value is assigned to the value object, the default assumption is that the new value
-
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
-
# converted to an instance of value class if necessary.
-
#
-
# For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
-
# aggregated using the +NetAddr::CIDR+ value class (https://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
-
# The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
-
# New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
-
# or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
-
# these requirements:
-
#
-
# class NetworkResource < ActiveRecord::Base
-
# composed_of :cidr,
-
# class_name: 'NetAddr::CIDR',
-
# mapping: [ %w(network_address network), %w(cidr_range bits) ],
-
# allow_nil: true,
-
# constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
-
# converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
-
# end
-
#
-
# # This calls the :constructor
-
# network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
-
#
-
# # These assignments will both use the :converter
-
# network_resource.cidr = [ '192.168.2.1', 8 ]
-
# network_resource.cidr = '192.168.0.1/24'
-
#
-
# # This assignment won't use the :converter as the value is already an instance of the value class
-
# network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
-
#
-
# # Saving and then reloading will use the :constructor on reload
-
# network_resource.save
-
# network_resource.reload
-
#
-
# == Finding records by a value object
-
#
-
# Once a #composed_of relationship is specified for a model, records can be loaded from the database
-
# by specifying an instance of the value object in the conditions hash. The following example
-
# finds all customers with +address_street+ equal to "May Street" and +address_city+ equal to "Chicago":
-
#
-
# Customer.where(address: Address.new("May Street", "Chicago"))
-
#
-
3
module ClassMethods
-
# Adds reader and writer methods for manipulating a value object:
-
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
-
#
-
# Options are:
-
# * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
-
# can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
-
# to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it
-
# with this option.
-
# * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
-
# object. Each mapping is represented as an array where the first item is the name of the
-
# entity attribute and the second item is the name of the attribute in the value object. The
-
# order in which mappings are defined determines the order in which attributes are sent to the
-
# value class constructor.
-
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
-
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
-
# mapped attributes.
-
# This defaults to +false+.
-
# * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
-
# is called to initialize the value object. The constructor is passed all of the mapped attributes,
-
# in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
-
# to instantiate a <tt>:class_name</tt> object.
-
# The default is <tt>:new</tt>.
-
# * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
-
# or a Proc that is called when a new value is assigned to the value object. The converter is
-
# passed the single value that is used in the assignment and is only called if the new value is
-
# not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
-
# can return +nil+ to skip the assignment.
-
#
-
# Option examples:
-
# composed_of :temperature, mapping: %w(reading celsius)
-
# composed_of :balance, class_name: "Money", mapping: %w(balance amount)
-
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
-
# composed_of :gps_location
-
# composed_of :gps_location, allow_nil: true
-
# composed_of :ip_address,
-
# class_name: 'IPAddr',
-
# mapping: %w(ip to_i),
-
# constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
-
# converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
-
#
-
3
def composed_of(part_id, options = {})
-
27
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
-
-
27
unless self < Aggregations
-
9
include Aggregations
-
end
-
-
27
name = part_id.id2name
-
27
class_name = options[:class_name] || name.camelize
-
27
mapping = options[:mapping] || [ name, name ]
-
27
mapping = [ mapping ] unless mapping.first.is_a?(Array)
-
27
allow_nil = options[:allow_nil] || false
-
27
constructor = options[:constructor] || :new
-
27
converter = options[:converter]
-
-
27
reader_method(name, class_name, mapping, allow_nil, constructor)
-
27
writer_method(name, class_name, mapping, allow_nil, converter)
-
-
27
reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
-
27
Reflection.add_aggregate_reflection self, part_id, reflection
-
end
-
-
3
private
-
3
def reader_method(name, class_name, mapping, allow_nil, constructor)
-
27
define_method(name) do
-
261
if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? })
-
258
attrs = mapping.collect { |key, _| _read_attribute(key) }
-
90
object = constructor.respond_to?(:call) ?
-
constructor.call(*attrs) :
-
class_name.constantize.send(constructor, *attrs)
-
90
@aggregation_cache[name] = object
-
end
-
177
@aggregation_cache[name]
-
end
-
end
-
-
3
def writer_method(name, class_name, mapping, allow_nil, converter)
-
27
define_method("#{name}=") do |part|
-
63
klass = class_name.constantize
-
-
63
unless part.is_a?(klass) || converter.nil? || part.nil?
-
9
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
-
end
-
-
63
hash_from_multiparameter_assignment = part.is_a?(Hash) &&
-
45
part.each_key.all? { |k| k.is_a?(Integer) }
-
63
if hash_from_multiparameter_assignment
-
15
raise ArgumentError unless part.size == part.each_key.max
-
9
part = klass.new(*part.sort.map(&:last))
-
end
-
-
57
if part.nil? && allow_nil
-
60
mapping.each { |key, _| self[key] = nil }
-
21
@aggregation_cache[name] = nil
-
else
-
90
mapping.each { |key, value| self[key] = part.send(value) }
-
30
@aggregation_cache[name] = part.freeze
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class AssociationRelation < Relation # :nodoc:
-
3
def initialize(klass, association, **)
-
15191
super(klass)
-
15191
@association = association
-
end
-
-
3
def proxy_association
-
78
@association
-
end
-
-
3
def ==(other)
-
75
other == records
-
end
-
-
3
def build(attributes = nil, &block)
-
39
block = _deprecated_scope_block("new", &block)
-
78
scoping { @association.build(attributes, &block) }
-
end
-
3
alias new build
-
-
3
def create(attributes = nil, &block)
-
18
block = _deprecated_scope_block("create", &block)
-
36
scoping { @association.create(attributes, &block) }
-
end
-
-
3
def create!(attributes = nil, &block)
-
12
block = _deprecated_scope_block("create!", &block)
-
24
scoping { @association.create!(attributes, &block) }
-
end
-
-
3
%w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
-
18
class_eval <<~RUBY
-
def #{method}(attributes, **kwargs)
-
if @association.reflection.through_reflection?
-
raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
-
end
-
-
scoping { klass.#{method}(attributes, **kwargs) }
-
end
-
RUBY
-
end
-
-
3
private
-
3
def exec_queries
-
2874
super do |record|
-
4483
@association.set_inverse_instance_from_queries(record)
-
4483
yield record if block_given?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
3
require "active_support/core_ext/string/conversions"
-
-
3
module ActiveRecord
-
3
class AssociationNotFoundError < ConfigurationError #:nodoc:
-
3
attr_reader :record, :association_name
-
3
def initialize(record = nil, association_name = nil)
-
18
@record = record
-
18
@association_name = association_name
-
18
if record && association_name
-
15
super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
-
else
-
3
super("Association was not found.")
-
end
-
end
-
-
3
class Correction
-
3
def initialize(error)
-
@error = error
-
end
-
-
3
def corrections
-
if @error.association_name
-
maybe_these = @error.record.class.reflections.keys
-
-
maybe_these.sort_by { |n|
-
DidYouMean::Jaro.distance(@error.association_name.to_s, n)
-
}.reverse.first(4)
-
else
-
[]
-
end
-
end
-
end
-
-
# We may not have DYM, and DYM might not let us register error handlers
-
3
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
-
DidYouMean.correct_error(self, Correction)
-
end
-
end
-
-
3
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
-
3
def initialize(reflection = nil, associated_class = nil)
-
18
if reflection
-
15
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
-
else
-
3
super("Could not find the inverse association.")
-
end
-
end
-
end
-
-
3
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
-
3
attr_reader :owner_class, :reflection
-
-
3
def initialize(owner_class = nil, reflection = nil)
-
6
if owner_class && reflection
-
3
@owner_class = owner_class
-
3
@reflection = reflection
-
3
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
-
else
-
3
super("Could not find the association.")
-
end
-
end
-
-
3
class Correction
-
3
def initialize(error)
-
@error = error
-
end
-
-
3
def corrections
-
if @error.reflection && @error.owner_class
-
maybe_these = @error.owner_class.reflections.keys
-
maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
-
-
maybe_these.sort_by { |n|
-
DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
-
}.reverse.first(4)
-
else
-
[]
-
end
-
end
-
end
-
-
# We may not have DYM, and DYM might not let us register error handlers
-
3
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
-
DidYouMean.correct_error(self, Correction)
-
end
-
end
-
-
3
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
-
3
def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
-
6
if owner_class_name && reflection && source_reflection
-
3
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
-
else
-
3
super("Cannot have a has_many :through association.")
-
end
-
end
-
end
-
-
3
class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
-
3
def initialize(owner_class_name = nil, reflection = nil)
-
6
if owner_class_name && reflection
-
3
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
-
else
-
3
super("Cannot have a has_many :through association.")
-
end
-
end
-
end
-
-
3
class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
-
3
def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
-
3
if owner_class_name && reflection && source_reflection
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
-
else
-
3
super("Cannot have a has_many :through association.")
-
end
-
end
-
end
-
-
3
class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
-
3
def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
-
6
if owner_class_name && reflection && through_reflection
-
3
super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
-
else
-
3
super("Cannot have a has_one :through association.")
-
end
-
end
-
end
-
-
3
class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
-
3
def initialize(owner_class_name = nil, reflection = nil)
-
6
if owner_class_name && reflection
-
3
super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
-
else
-
3
super("Cannot have a has_one :through association.")
-
end
-
end
-
end
-
-
3
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
-
3
def initialize(reflection = nil)
-
3
if reflection
-
through_reflection = reflection.through_reflection
-
source_reflection_names = reflection.source_reflection_names
-
source_associations = reflection.through_reflection.klass._reflections.keys
-
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}?")
-
else
-
3
super("Could not find the source association(s).")
-
end
-
end
-
end
-
-
3
class HasManyThroughOrderError < ActiveRecordError #:nodoc:
-
3
def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
-
6
if owner_class_name && reflection && through_reflection
-
3
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.")
-
else
-
3
super("Cannot have a has_many :through association before the through association is defined.")
-
end
-
end
-
end
-
-
3
class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
-
3
def initialize(owner = nil, reflection = nil)
-
30
if owner && reflection
-
21
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
-
else
-
9
super("Cannot modify association.")
-
end
-
end
-
end
-
-
3
class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc:
-
3
def initialize(klass, macro, association_name, options, possible_sources)
-
example_options = options.dup
-
example_options[:source] = possible_sources.first
-
-
super("Ambiguous source reflection for through association. Please " \
-
"specify a :source directive on your declaration like:\n" \
-
"\n" \
-
" class #{klass} < ActiveRecord::Base\n" \
-
" #{macro} :#{association_name}, #{example_options}\n" \
-
" end"
-
)
-
end
-
end
-
-
3
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
-
end
-
-
3
class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
-
end
-
-
3
class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
-
3
def initialize(owner = nil, reflection = nil)
-
33
if owner && reflection
-
24
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
-
else
-
9
super("Through nested associations are read-only.")
-
end
-
end
-
end
-
-
3
class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
-
end
-
-
3
class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
-
end
-
-
# This error is raised when trying to eager load a polymorphic association using a JOIN.
-
# Eager loading polymorphic associations is only possible with
-
# {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload].
-
3
class EagerLoadPolymorphicError < ActiveRecordError
-
3
def initialize(reflection = nil)
-
15
if reflection
-
12
super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
-
else
-
3
super("Eager load polymorphic error.")
-
end
-
end
-
end
-
-
# This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
-
# (has_many, has_one) when there is at least 1 child associated instance.
-
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
-
3
class DeleteRestrictionError < ActiveRecordError #:nodoc:
-
3
def initialize(name = nil)
-
9
if name
-
6
super("Cannot delete record because of dependent #{name}")
-
else
-
3
super("Delete restriction error.")
-
end
-
end
-
end
-
-
# See ActiveRecord::Associations::ClassMethods for documentation.
-
3
module Associations # :nodoc:
-
3
extend ActiveSupport::Autoload
-
3
extend ActiveSupport::Concern
-
-
# These classes will be loaded when associations are created.
-
# So there is no need to eager load them.
-
3
autoload :Association
-
3
autoload :SingularAssociation
-
3
autoload :CollectionAssociation
-
3
autoload :ForeignAssociation
-
3
autoload :CollectionProxy
-
3
autoload :ThroughAssociation
-
-
3
module Builder #:nodoc:
-
3
autoload :Association, "active_record/associations/builder/association"
-
3
autoload :SingularAssociation, "active_record/associations/builder/singular_association"
-
3
autoload :CollectionAssociation, "active_record/associations/builder/collection_association"
-
-
3
autoload :BelongsTo, "active_record/associations/builder/belongs_to"
-
3
autoload :HasOne, "active_record/associations/builder/has_one"
-
3
autoload :HasMany, "active_record/associations/builder/has_many"
-
3
autoload :HasAndBelongsToMany, "active_record/associations/builder/has_and_belongs_to_many"
-
end
-
-
3
eager_autoload do
-
3
autoload :BelongsToAssociation
-
3
autoload :BelongsToPolymorphicAssociation
-
3
autoload :HasManyAssociation
-
3
autoload :HasManyThroughAssociation
-
3
autoload :HasOneAssociation
-
3
autoload :HasOneThroughAssociation
-
-
3
autoload :Preloader
-
3
autoload :JoinDependency
-
3
autoload :AssociationScope
-
3
autoload :AliasTracker
-
end
-
-
3
def self.eager_load!
-
super
-
Preloader.eager_load!
-
end
-
-
# Returns the association instance for the given name, instantiating it if it doesn't already exist
-
3
def association(name) #:nodoc:
-
665346
association = association_instance_get(name)
-
-
665346
if association.nil?
-
228010
unless reflection = self.class._reflect_on_association(name)
-
15
raise AssociationNotFoundError.new(self, name)
-
end
-
227995
association = reflection.association_class.new(self, reflection)
-
227968
association_instance_set(name, association)
-
end
-
-
665304
association
-
end
-
-
3
def association_cached?(name) # :nodoc:
-
1084
@association_cache.key?(name)
-
end
-
-
3
def initialize_dup(*) # :nodoc:
-
111
@association_cache = {}
-
111
super
-
end
-
-
3
def reload(*) # :nodoc:
-
2233
clear_association_cache
-
2233
super
-
end
-
-
3
private
-
# Clears out the association cache.
-
3
def clear_association_cache
-
2233
@association_cache.clear if persisted?
-
end
-
-
3
def init_internals
-
261858
@association_cache = {}
-
261858
super
-
end
-
-
# Returns the specified association instance if it exists, +nil+ otherwise.
-
3
def association_instance_get(name)
-
973015
@association_cache[name]
-
end
-
-
# Set the specified association instance.
-
3
def association_instance_set(name, association)
-
227968
@association_cache[name] = association
-
end
-
-
# \Associations are a set of macro-like class methods for tying objects together through
-
# foreign keys. They express relationships like "Project has one Project Manager"
-
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
-
# class which are specialized according to the collection or association symbol and the
-
# options hash. It works much the same way as Ruby's own <tt>attr*</tt>
-
# methods.
-
#
-
# class Project < ActiveRecord::Base
-
# belongs_to :portfolio
-
# has_one :project_manager
-
# has_many :milestones
-
# has_and_belongs_to_many :categories
-
# end
-
#
-
# The project class now has the following methods (and more) to ease the traversal and
-
# manipulation of its relationships:
-
# * <tt>Project#portfolio</tt>, <tt>Project#portfolio=(portfolio)</tt>, <tt>Project#reload_portfolio</tt>
-
# * <tt>Project#project_manager</tt>, <tt>Project#project_manager=(project_manager)</tt>, <tt>Project#reload_project_manager</tt>
-
# * <tt>Project#milestones.empty?</tt>, <tt>Project#milestones.size</tt>, <tt>Project#milestones</tt>, <tt>Project#milestones<<(milestone)</tt>,
-
# <tt>Project#milestones.delete(milestone)</tt>, <tt>Project#milestones.destroy(milestone)</tt>, <tt>Project#milestones.find(milestone_id)</tt>,
-
# <tt>Project#milestones.build</tt>, <tt>Project#milestones.create</tt>
-
# * <tt>Project#categories.empty?</tt>, <tt>Project#categories.size</tt>, <tt>Project#categories</tt>, <tt>Project#categories<<(category1)</tt>,
-
# <tt>Project#categories.delete(category1)</tt>, <tt>Project#categories.destroy(category1)</tt>
-
#
-
# === A word of warning
-
#
-
# Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of
-
# <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
-
# its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things.
-
# For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods.
-
#
-
# == Auto-generated methods
-
# See also Instance Public methods below for more details.
-
#
-
# === Singular associations (one-to-one)
-
# | | belongs_to |
-
# generated methods | belongs_to | :polymorphic | has_one
-
# ----------------------------------+------------+--------------+---------
-
# other | X | X | X
-
# other=(other) | X | X | X
-
# build_other(attributes={}) | X | | X
-
# create_other(attributes={}) | X | | X
-
# create_other!(attributes={}) | X | | X
-
# reload_other | X | X | X
-
#
-
# === Collection associations (one-to-many / many-to-many)
-
# | | | has_many
-
# generated methods | habtm | has_many | :through
-
# ----------------------------------+-------+----------+----------
-
# others | X | X | X
-
# others=(other,other,...) | X | X | X
-
# other_ids | X | X | X
-
# other_ids=(id,id,...) | X | X | X
-
# others<< | X | X | X
-
# others.push | X | X | X
-
# others.concat | X | X | X
-
# others.build(attributes={}) | X | X | X
-
# others.create(attributes={}) | X | X | X
-
# others.create!(attributes={}) | X | X | X
-
# others.size | X | X | X
-
# others.length | X | X | X
-
# others.count | X | X | X
-
# others.sum(*args) | X | X | X
-
# others.empty? | X | X | X
-
# others.clear | X | X | X
-
# others.delete(other,other,...) | X | X | X
-
# others.delete_all | X | X | X
-
# others.destroy(other,other,...) | X | X | X
-
# others.destroy_all | X | X | X
-
# others.find(*args) | X | X | X
-
# others.exists? | X | X | X
-
# others.distinct | X | X | X
-
# others.reset | X | X | X
-
# others.reload | X | X | X
-
#
-
# === Overriding generated methods
-
#
-
# Association methods are generated in a module included into the model
-
# class, making overrides easy. The original generated method can thus be
-
# called with +super+:
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :owner
-
# belongs_to :old_owner
-
#
-
# def owner=(new_owner)
-
# self.old_owner = self.owner
-
# super
-
# end
-
# end
-
#
-
# The association methods module is included immediately after the
-
# generated attributes methods module, meaning an association will
-
# override the methods for an attribute with the same name.
-
#
-
# == Cardinality and associations
-
#
-
# Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
-
# relationships between models. Each model uses an association to describe its role in
-
# the relation. The #belongs_to association is always used in the model that has
-
# the foreign key.
-
#
-
# === One-to-one
-
#
-
# Use #has_one in the base, and #belongs_to in the associated model.
-
#
-
# class Employee < ActiveRecord::Base
-
# has_one :office
-
# end
-
# class Office < ActiveRecord::Base
-
# belongs_to :employee # foreign key - employee_id
-
# end
-
#
-
# === One-to-many
-
#
-
# Use #has_many in the base, and #belongs_to in the associated model.
-
#
-
# class Manager < ActiveRecord::Base
-
# has_many :employees
-
# end
-
# class Employee < ActiveRecord::Base
-
# belongs_to :manager # foreign key - manager_id
-
# end
-
#
-
# === Many-to-many
-
#
-
# There are two ways to build a many-to-many relationship.
-
#
-
# The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so
-
# there are two stages of associations.
-
#
-
# class Assignment < ActiveRecord::Base
-
# belongs_to :programmer # foreign key - programmer_id
-
# belongs_to :project # foreign key - project_id
-
# end
-
# class Programmer < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :projects, through: :assignments
-
# end
-
# class Project < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :programmers, through: :assignments
-
# end
-
#
-
# For the second way, use #has_and_belongs_to_many in both models. This requires a join table
-
# that has no corresponding model or primary key.
-
#
-
# class Programmer < ActiveRecord::Base
-
# has_and_belongs_to_many :projects # foreign keys in the join table
-
# end
-
# class Project < ActiveRecord::Base
-
# has_and_belongs_to_many :programmers # foreign keys in the join table
-
# end
-
#
-
# Choosing which way to build a many-to-many relationship is not always simple.
-
# If you need to work with the relationship model as its own entity,
-
# use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when
-
# you never work directly with the relationship itself.
-
#
-
# == Is it a #belongs_to or #has_one association?
-
#
-
# Both express a 1-1 relationship. The difference is mostly where to place the foreign
-
# key, which goes on the table for the class declaring the #belongs_to relationship.
-
#
-
# class User < ActiveRecord::Base
-
# # I reference an account.
-
# belongs_to :account
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# # One user references me.
-
# has_one :user
-
# end
-
#
-
# The tables for these classes could look something like:
-
#
-
# CREATE TABLE users (
-
# id bigint NOT NULL auto_increment,
-
# account_id bigint default NULL,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# CREATE TABLE accounts (
-
# id bigint NOT NULL auto_increment,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# == Unsaved objects and associations
-
#
-
# You can manipulate objects and associations before they are saved to the database, but
-
# there is some special behavior you should be aware of, mostly involving the saving of
-
# associated objects.
-
#
-
# You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to,
-
# #has_many, or #has_and_belongs_to_many association. Setting it
-
# to +true+ will _always_ save the members, whereas setting it to +false+ will
-
# _never_ save the members. More details about <tt>:autosave</tt> option is available at
-
# AutosaveAssociation.
-
#
-
# === One-to-one associations
-
#
-
# * Assigning an object to a #has_one association automatically saves that object and
-
# the object being replaced (if there is one), in order to update their foreign
-
# keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
-
# * If either of these saves fail (due to one of the objects being invalid), an
-
# ActiveRecord::RecordNotSaved exception is raised and the assignment is
-
# cancelled.
-
# * If you wish to assign an object to a #has_one association without saving it,
-
# use the <tt>#build_association</tt> method (documented below). The object being
-
# replaced will still be saved to update its foreign key.
-
# * Assigning an object to a #belongs_to association does not save the object, since
-
# the foreign key field belongs on the parent. It does not save the parent either.
-
#
-
# === Collections
-
#
-
# * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically
-
# saves that object, except if the parent object (the owner of the collection) is not yet
-
# stored in the database.
-
# * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
-
# fails, then <tt>push</tt> returns +false+.
-
# * If saving fails while replacing the collection (via <tt>association=</tt>), an
-
# ActiveRecord::RecordNotSaved exception is raised and the assignment is
-
# cancelled.
-
# * You can add an object to a collection without automatically saving it by using the
-
# <tt>collection.build</tt> method (documented below).
-
# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
-
# saved when the parent is saved.
-
#
-
# == Customizing the query
-
#
-
# \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
-
# to customize them. For example, to add a condition:
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
-
# end
-
#
-
# Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods.
-
#
-
# === Accessing the owner object
-
#
-
# Sometimes it is useful to have access to the owner object when building the query. The owner
-
# is passed as a parameter to the block. For example, the following association would find all
-
# events that occur on the user's birthday:
-
#
-
# class User < ActiveRecord::Base
-
# has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
-
# end
-
#
-
# Note: Joining, eager loading and preloading of these associations is not possible.
-
# These operations happen before instance creation and the scope will be called with a +nil+ argument.
-
#
-
# == Association callbacks
-
#
-
# Similar to the normal callbacks that hook into the life cycle of an Active Record object,
-
# you can also define callbacks that get triggered when you add an object to or remove an
-
# object from an association collection.
-
#
-
# class Project
-
# has_and_belongs_to_many :developers, after_add: :evaluate_velocity
-
#
-
# def evaluate_velocity(developer)
-
# ...
-
# end
-
# end
-
#
-
# It's possible to stack callbacks by passing them as an array. Example:
-
#
-
# class Project
-
# has_and_belongs_to_many :developers,
-
# after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
-
# end
-
#
-
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
-
#
-
# If any of the +before_add+ callbacks throw an exception, the object will not be
-
# added to the collection.
-
#
-
# Similarly, if any of the +before_remove+ callbacks throw an exception, the object
-
# will not be removed from the collection.
-
#
-
# == Association extensions
-
#
-
# The proxy objects that control the access to associations can be extended through anonymous
-
# modules. This is especially beneficial for adding new finders, creators, and other
-
# factory-type methods that are only used as part of this association.
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
# end
-
#
-
# person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
-
# person.first_name # => "David"
-
# person.last_name # => "Heinemeier Hansson"
-
#
-
# If you need to share the same extensions between many associations, you can use a named
-
# extension module.
-
#
-
# module FindOrCreateByNameExtension
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# class Company < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# Some extensions can only be made to work with knowledge of the association's internals.
-
# Extensions can access relevant state using the following methods (where +items+ is the
-
# name of the association):
-
#
-
# * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
-
# * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
-
# * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or
-
# the collection of associated objects for #has_many and #has_and_belongs_to_many.
-
#
-
# However, inside the actual extension code, you will not have access to the <tt>record</tt> as
-
# above. In this case, you can access <tt>proxy_association</tt>. For example,
-
# <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
-
# the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
-
# association extensions.
-
#
-
# == Association Join Models
-
#
-
# Has Many associations can be configured with the <tt>:through</tt> option to use an
-
# explicit join model to retrieve the data. This operates similarly to a
-
# #has_and_belongs_to_many association. The advantage is that you're able to add validations,
-
# callbacks, and extra attributes on the join model. Consider the following schema:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :authorships
-
# has_many :books, through: :authorships
-
# end
-
#
-
# class Authorship < ActiveRecord::Base
-
# belongs_to :author
-
# belongs_to :book
-
# end
-
#
-
# @author = Author.first
-
# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
-
# @author.books # selects all books by using the Authorship join model
-
#
-
# You can also go through a #has_many association on the join model:
-
#
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# has_many :invoices, through: :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base
-
# belongs_to :firm
-
# has_many :invoices
-
# end
-
#
-
# class Invoice < ActiveRecord::Base
-
# belongs_to :client
-
# end
-
#
-
# @firm = Firm.first
-
# @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
-
# @firm.invoices # selects all invoices by going through the Client join model
-
#
-
# Similarly you can go through a #has_one association on the join model:
-
#
-
# class Group < ActiveRecord::Base
-
# has_many :users
-
# has_many :avatars, through: :users
-
# end
-
#
-
# class User < ActiveRecord::Base
-
# belongs_to :group
-
# has_one :avatar
-
# end
-
#
-
# class Avatar < ActiveRecord::Base
-
# belongs_to :user
-
# end
-
#
-
# @group = Group.first
-
# @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
-
# @group.avatars # selects all avatars by going through the User join model.
-
#
-
# An important caveat with going through #has_one or #has_many associations on the
-
# join model is that these associations are *read-only*. For example, the following
-
# would not work following the previous example:
-
#
-
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
-
# @group.avatars.delete(@group.avatars.last) # so would this
-
#
-
# == Setting Inverses
-
#
-
# If you are using a #belongs_to on the join model, it is a good idea to set the
-
# <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example
-
# works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association):
-
#
-
# @post = Post.first
-
# @tag = @post.tags.build name: "ruby"
-
# @tag.save
-
#
-
# The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the
-
# <tt>:inverse_of</tt> is set:
-
#
-
# class Tagging < ActiveRecord::Base
-
# belongs_to :post
-
# belongs_to :tag, inverse_of: :taggings
-
# end
-
#
-
# If you do not set the <tt>:inverse_of</tt> record, the association will
-
# do its best to match itself up with the correct inverse. Automatic
-
# inverse detection only works on #has_many, #has_one, and
-
# #belongs_to associations.
-
#
-
# <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations,
-
# or a custom scope, will also prevent the association's inverse
-
# from being found automatically.
-
#
-
# The automatic guessing of the inverse association uses a heuristic based
-
# on the name of the class, so it may not work for all associations,
-
# especially the ones with non-standard names.
-
#
-
# You can turn off the automatic detection of inverse associations by setting
-
# the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
-
#
-
# class Tagging < ActiveRecord::Base
-
# belongs_to :tag, inverse_of: false
-
# end
-
#
-
# == Nested \Associations
-
#
-
# You can actually specify *any* association with the <tt>:through</tt> option, including an
-
# association which has a <tt>:through</tt> option itself. For example:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :comments, through: :posts
-
# has_many :commenters, through: :comments
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# @author = Author.first
-
# @author.commenters # => People who commented on posts written by the author
-
#
-
# An equivalent way of setting up this association this would be:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :commenters, through: :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# has_many :commenters, through: :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# When using a nested association, you will not be able to modify the association because there
-
# is not enough information to know what modification to make. For example, if you tried to
-
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
-
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
-
#
-
# == Polymorphic \Associations
-
#
-
# Polymorphic associations on models are not restricted on what types of models they
-
# can be associated with. Rather, they specify an interface that a #has_many association
-
# must adhere to.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, polymorphic: true
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
-
# end
-
#
-
# @asset.attachable = @post
-
#
-
# This works by using a type column in addition to a foreign key to specify the associated
-
# record. In the Asset example, you'd need an +attachable_id+ integer column and an
-
# +attachable_type+ string column.
-
#
-
# Using polymorphic associations in combination with single table inheritance (STI) is
-
# a little tricky. In order for the associations to work as expected, ensure that you
-
# store the base model for the STI models in the type column of the polymorphic
-
# association. To continue with the asset example above, suppose there are guest posts
-
# and member posts that use the posts table for STI. In this case, there must be a +type+
-
# column in the posts table.
-
#
-
# Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
-
# The +class_name+ of the +attachable+ is passed as a String.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, polymorphic: true
-
#
-
# def attachable_type=(class_name)
-
# super(class_name.constantize.base_class.to_s)
-
# end
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# # because we store "Post" in attachable_type now dependent: :destroy will work
-
# has_many :assets, as: :attachable, dependent: :destroy
-
# end
-
#
-
# class GuestPost < Post
-
# end
-
#
-
# class MemberPost < Post
-
# end
-
#
-
# == Caching
-
#
-
# All of the methods are built on a simple caching principle that will keep the result
-
# of the last query around unless specifically instructed not to. The cache is even
-
# shared across methods to make it even cheaper to use the macro-added methods without
-
# worrying too much about performance at the first go.
-
#
-
# project.milestones # fetches milestones from the database
-
# project.milestones.size # uses the milestone cache
-
# project.milestones.empty? # uses the milestone cache
-
# project.milestones.reload.size # fetches milestones from the database
-
# project.milestones # uses the milestone cache
-
#
-
# == Eager loading of associations
-
#
-
# Eager loading is a way to find objects of a certain class and a number of named associations.
-
# It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100
-
# posts that each need to display their author triggers 101 database queries. Through the
-
# use of eager loading, the number of queries will be reduced from 101 to 2.
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# has_many :comments
-
# end
-
#
-
# Consider the following loop using the class above:
-
#
-
# Post.all.each do |post|
-
# puts "Post: " + post.title
-
# puts "Written by: " + post.author.name
-
# puts "Last comment on: " + post.comments.first.created_on
-
# end
-
#
-
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's
-
# first just optimize it for retrieving the author:
-
#
-
# Post.includes(:author).each do |post|
-
#
-
# This references the name of the #belongs_to association that also used the <tt>:author</tt>
-
# symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load
-
# all of the referenced authors with one query. Doing so will cut down the number of queries
-
# from 201 to 102.
-
#
-
# We can improve upon the situation further by referencing both associations in the finder with:
-
#
-
# Post.includes(:author, :comments).each do |post|
-
#
-
# This will load all comments with a single query. This reduces the total number of queries
-
# to 3. In general, the number of queries will be 1 plus the number of associations
-
# named (except if some of the associations are polymorphic #belongs_to - see below).
-
#
-
# To include a deep hierarchy of associations, use a hash:
-
#
-
# Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
-
#
-
# The above code will load all the comments and all of their associated
-
# authors and gravatars. You can mix and match any combination of symbols,
-
# arrays, and hashes to retrieve the associations you want to load.
-
#
-
# All of this power shouldn't fool you into thinking that you can pull out huge amounts
-
# of data with no performance penalty just because you've reduced the number of queries.
-
# The database still needs to send all the data to Active Record and it still needs to
-
# be processed. So it's no catch-all for performance problems, but it's a great way to
-
# cut down on the number of queries in a situation as the one described above.
-
#
-
# Since only one table is loaded at a time, conditions or orders cannot reference tables
-
# other than the main one. If this is the case, Active Record falls back to the previously
-
# used <tt>LEFT OUTER JOIN</tt> based strategy. For example:
-
#
-
# Post.includes([:author, :comments]).where(['comments.approved = ?', true])
-
#
-
# This will result in a single SQL query with joins along the lines of:
-
# <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
-
# <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
-
# like this can have unintended consequences.
-
# In the above example, posts with no approved comments are not returned at all because
-
# the conditions apply to the SQL statement as a whole and not just to the association.
-
#
-
# You must disambiguate column references for this fallback to happen, for example
-
# <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
-
#
-
# If you want to load all posts (including posts with no approved comments), then write
-
# your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>:
-
#
-
# Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
-
#
-
# In this case, it is usually more natural to include an association which has conditions defined on it:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
-
# end
-
#
-
# Post.includes(:approved_comments)
-
#
-
# This will load posts and eager load the +approved_comments+ association, which contains
-
# only those comments that have been approved.
-
#
-
# If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
-
# returning all the associated objects:
-
#
-
# class Picture < ActiveRecord::Base
-
# has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
-
# end
-
#
-
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
-
#
-
# Eager loading is supported with polymorphic associations.
-
#
-
# class Address < ActiveRecord::Base
-
# belongs_to :addressable, polymorphic: true
-
# end
-
#
-
# A call that tries to eager load the addressable model
-
#
-
# Address.includes(:addressable)
-
#
-
# This will execute one query to load the addresses and load the addressables with one
-
# query per addressable type.
-
# For example, if all the addressables are either of class Person or Company, then a total
-
# of 3 queries will be executed. The list of addressable types to load is determined on
-
# the back of the addresses loaded. This is not supported if Active Record has to fallback
-
# to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
-
# The reason is that the parent model's type is a column value so its corresponding table
-
# name cannot be put in the +FROM+/+JOIN+ clauses of that query.
-
#
-
# == Table Aliasing
-
#
-
# Active Record uses table aliasing in the case that a table is referenced multiple times
-
# in a join. If a table is referenced only once, the standard table name is used. The
-
# second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
-
# Indexes are appended for any more successive uses of the table name.
-
#
-
# Post.joins(:comments)
-
# # => SELECT ... FROM posts INNER JOIN comments ON ...
-
# Post.joins(:special_comments) # STI
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
-
# Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
-
#
-
# Acts as tree example:
-
#
-
# TreeMixin.joins(:children)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# TreeMixin.joins(children: :parent)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# TreeMixin.joins(children: {parent: :children})
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# INNER JOIN mixins childrens_mixins_2
-
#
-
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
-
#
-
# Post.joins(:categories)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# Post.joins(categories: :posts)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# Post.joins(categories: {posts: :categories})
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
-
#
-
# If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table
-
# names will take precedence over the eager associations:
-
#
-
# Post.joins(:comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
-
# Post.joins(:comments, :special_comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
-
# INNER JOIN comments special_comments_posts ...
-
# INNER JOIN comments ...
-
#
-
# Table aliases are automatically truncated according to the maximum length of table identifiers
-
# according to the specific database.
-
#
-
# == Modules
-
#
-
# By default, associations will look for objects within the current module scope. Consider:
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base; end
-
# end
-
# end
-
#
-
# When <tt>Firm#clients</tt> is called, it will in turn call
-
# <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
-
# If you want to associate with a class in another module scope, this can be done by
-
# specifying the complete class name.
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base; end
-
# end
-
#
-
# module Billing
-
# class Account < ActiveRecord::Base
-
# belongs_to :firm, class_name: "MyApplication::Business::Firm"
-
# end
-
# end
-
# end
-
#
-
# == Bi-directional associations
-
#
-
# When you specify an association, there is usually an association on the associated model
-
# that specifies the same relationship in reverse. For example, with the following models:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps
-
# has_one :evil_wizard
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
-
# the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+
-
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
-
# Active Record can guess the inverse of the association based on the name
-
# of the class. The result is the following:
-
#
-
# d = Dungeon.first
-
# t = d.traps.first
-
# d.object_id == t.dungeon.object_id # => true
-
#
-
# The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
-
# the same in-memory instance since the association matches the name of the class.
-
# The result would be the same if we added +:inverse_of+ to our model definitions:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps, inverse_of: :dungeon
-
# has_one :evil_wizard, inverse_of: :dungeon
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon, inverse_of: :traps
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon, inverse_of: :evil_wizard
-
# end
-
#
-
# For more information, see the documentation for the +:inverse_of+ option.
-
#
-
# == Deleting from associations
-
#
-
# === Dependent associations
-
#
-
# #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option.
-
# This allows you to specify that associated records should be deleted when the owner is
-
# deleted.
-
#
-
# For example:
-
#
-
# class Author
-
# has_many :posts, dependent: :destroy
-
# end
-
# Author.find(1).destroy # => Will destroy all of the author's posts, too
-
#
-
# The <tt>:dependent</tt> option can have different values which specify how the deletion
-
# is done. For more information, see the documentation for this option on the different
-
# specific association types. When no option is given, the behavior is to do nothing
-
# with the associated records when destroying a record.
-
#
-
# Note that <tt>:dependent</tt> is implemented using Rails' callback
-
# system, which works by processing callbacks in order. Therefore, other
-
# callbacks declared either before or after the <tt>:dependent</tt> option
-
# can affect what it does.
-
#
-
# Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations.
-
#
-
# === Delete or destroy?
-
#
-
# #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>,
-
# <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
-
#
-
# For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they
-
# cause the records in the join table to be removed.
-
#
-
# For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
-
# record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
-
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
-
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
-
# The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
-
# #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
-
# the join records, without running their callbacks).
-
#
-
# There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
-
# it returns the association rather than the records which have been deleted.
-
#
-
# === What gets deleted?
-
#
-
# There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt>
-
# associations have records in join tables, as well as the associated records. So when we
-
# call one of these deletion methods, what exactly should be deleted?
-
#
-
# The answer is that it is assumed that deletion on an association is about removing the
-
# <i>link</i> between the owner and the associated object(s), rather than necessarily the
-
# associated objects themselves. So with #has_and_belongs_to_many and #has_many
-
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
-
#
-
# This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
-
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
-
# to be removed from the database.
-
#
-
# However, there are examples where this strategy doesn't make sense. For example, suppose
-
# a person has many projects, and each project has many tasks. If we deleted one of a person's
-
# tasks, we would probably not want the project to be deleted. In this scenario, the delete method
-
# won't actually work: it can only be used if the association on the join model is a
-
# #belongs_to. In other situations you are expected to perform operations directly on
-
# either the associated records or the <tt>:through</tt> association.
-
#
-
# With a regular #has_many there is no distinction between the "associated records"
-
# and the "link", so there is only one choice for what gets deleted.
-
#
-
# With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the
-
# associated records themselves, you can always do something along the lines of
-
# <tt>person.tasks.each(&:destroy)</tt>.
-
#
-
# == Type safety with ActiveRecord::AssociationTypeMismatch
-
#
-
# If you attempt to assign an object to an association that doesn't match the inferred
-
# or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch.
-
#
-
# == Options
-
#
-
# All of the association macros can be specialized through options. This makes cases
-
# more complex than the simple and guessable ones possible.
-
3
module ClassMethods
-
# Specifies a one-to-many association. The following methods for retrieval and query of
-
# collections of associated objects will be added:
-
#
-
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
-
#
-
# [collection]
-
# Returns a Relation of all the associated objects.
-
# An empty Relation is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
-
# Note that this operation instantly fires update SQL without waiting for the save or update call on the
-
# parent object, unless the parent object is a new record.
-
# This will also run validations and callbacks of associated object(s).
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
-
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
-
# and deleted if they're associated with <tt>dependent: :delete_all</tt>.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are deleted (rather than
-
# nullified) by default, but you can specify <tt>dependent: :destroy</tt> or
-
# <tt>dependent: :nullify</tt> to override this.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running <tt>destroy</tt> on
-
# each record, regardless of any dependent option, ensuring callbacks are run.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are destroyed
-
# instead, not the objects themselves.
-
# [collection=objects]
-
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
-
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
-
# direct by default. You can specify <tt>dependent: :destroy</tt> or
-
# <tt>dependent: :nullify</tt> to override this.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids
-
# [collection_singular_ids=ids]
-
# Replace the collection with the objects identified by the primary keys in +ids+. This
-
# method loads the models and calls <tt>collection=</tt>. See above.
-
# [collection.clear]
-
# Removes every object from the collection. This destroys the associated objects if they
-
# are associated with <tt>dependent: :destroy</tt>, deletes them directly from the
-
# database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
-
# If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
-
# Join models are directly deleted.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(...)]
-
# Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as ActiveRecord::FinderMethods#exists?.
-
# [collection.build(attributes = {}, ...)]
-
# Returns one or more new objects of the collection type that have been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but have not yet
-
# been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that has already
-
# been saved (if it passed the validation). *Note*: This only works if the base model
-
# already exists in the DB, not if it is a new (unsaved) record!
-
# [collection.create!(attributes = {})]
-
# Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid
-
# if the record is invalid.
-
# [collection.reload]
-
# Returns a Relation of all of the associated objects, forcing a database read.
-
# An empty Relation is returned if none are found.
-
#
-
# === Example
-
#
-
# A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
-
# * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
-
# * <tt>Firm#clients<<</tt>
-
# * <tt>Firm#clients.delete</tt>
-
# * <tt>Firm#clients.destroy</tt>
-
# * <tt>Firm#clients=</tt>
-
# * <tt>Firm#client_ids</tt>
-
# * <tt>Firm#client_ids=</tt>
-
# * <tt>Firm#clients.clear</tt>
-
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
-
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
-
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>)
-
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
-
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new(firm_id: id)</tt>)
-
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new(firm_id: id); c.save; c</tt>)
-
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new(firm_id: id); c.save!</tt>)
-
# * <tt>Firm#clients.reload</tt>
-
# The declaration can also include an +options+ hash to specialize the behavior of the association.
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific set of records or customize the generated
-
# query when you access the associated collection.
-
#
-
# Scope examples:
-
# has_many :comments, -> { where(author_id: 1) }
-
# has_many :employees, -> { joins(:address) }
-
# has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
-
#
-
# === Extensions
-
#
-
# The +extension+ argument allows you to pass a block into a has_many
-
# association. This is useful for adding new finders, creators and other
-
# factory-type methods to be used as part of the association.
-
#
-
# Extension examples:
-
# has_many :employees do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
#
-
# === Options
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_many :products</tt> will by default be linked
-
# to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to
-
# specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many
-
# association will use "person_id" as the default <tt>:foreign_key</tt>.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option.
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the polymorphic association
-
# specified on "as" option with a "_type" suffix. So a class that defines a
-
# <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the
-
# default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the name of the column to use as the primary key for the association. By default this is +id+.
-
# [:dependent]
-
# Controls what happens to the associated objects when
-
# their owner is destroyed. Note that these are implemented as
-
# callbacks, and Rails executes callbacks in order. Therefore, other
-
# similar callbacks may affect the <tt>:dependent</tt> behavior, and the
-
# <tt>:dependent</tt> behavior may affect other callbacks.
-
#
-
# * <tt>nil</tt> do nothing (default).
-
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
-
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
-
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
-
# on polymorphic associations. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there are any associated records.
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
-
#
-
# If using with the <tt>:through</tt> option, the association on the join model must be
-
# a #belongs_to, and the records which get deleted are the join records, rather than
-
# the associated records.
-
#
-
# If using <tt>dependent: :destroy</tt> on a scoped association, only the scoped objects are destroyed.
-
# For example, if a Post model defines
-
# <tt>has_many :comments, -> { where published: true }, dependent: :destroy</tt> and <tt>destroy</tt> is
-
# called on a post, only published comments are destroyed. This means that any unpublished comments in the
-
# database would still contain a foreign key pointing to the now deleted post.
-
# [:counter_cache]
-
# This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
-
# when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association.
-
# [:as]
-
# Specifies a polymorphic interface (See #belongs_to).
-
# [:through]
-
# Specifies an association through which to perform the query. This can be any other type
-
# of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection.
-
#
-
# If the association on the join model is a #belongs_to, the collection can be modified
-
# and the records on the <tt>:through</tt> model will be automatically created and removed
-
# as appropriate. Otherwise, the collection is read-only, so you should manipulate the
-
# <tt>:through</tt> association directly.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option on the source association on the
-
# join model. This allows associated records to be built which will automatically create
-
# the appropriate join model records when they are saved. (See the 'Association Join Models'
-
# section above.)
-
# [:source]
-
# Specifies the source association name used by #has_many <tt>:through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or
-
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source
-
# association is a polymorphic #belongs_to.
-
# [:validate]
-
# When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
-
# If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records. This option is implemented as a
-
# +before_save+ callback. Because callbacks are run in the order they are defined, associated objects
-
# may need to be explicitly saved in any user-defined +before_save+ callbacks.
-
#
-
# Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
-
# <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the #belongs_to association on the associated object
-
# that is the inverse of this #has_many association.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
# [:extend]
-
# Specifies a module or array of modules that will be extended into the association object returned.
-
# Useful for defining methods on associations, especially when they should be shared between multiple
-
# association objects.
-
# [:strict_loading]
-
# Enforces strict loading every time the associated record is loaded through this association.
-
#
-
# Option examples:
-
# has_many :comments, -> { order("posted_on") }
-
# has_many :comments, -> { includes(:author) }
-
# has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
-
# has_many :tracks, -> { order("position") }, dependent: :destroy
-
# has_many :comments, dependent: :nullify
-
# has_many :tags, as: :taggable
-
# has_many :reports, -> { readonly }
-
# has_many :subscribers, through: :subscriptions, source: :user
-
# has_many :comments, strict_loading: true
-
3
def has_many(name, scope = nil, **options, &extension)
-
1665
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
-
1650
Reflection.add_reflection self, name, reflection
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if the other class contains the foreign key. If the current class contains the foreign key,
-
# then you should use #belongs_to instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use #has_one and when to use #belongs_to.
-
#
-
# The following methods for retrieval and query of a single associated object will be added:
-
#
-
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
-
#
-
# [association]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
-
# and saves the associate object. To avoid database inconsistencies, permanently deletes an existing
-
# associated object when assigning a new one, even if the new one isn't saved to database.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not
-
# yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid
-
# if the record is invalid.
-
# [reload_association]
-
# Returns the associated object, forcing a database read.
-
#
-
# === Example
-
#
-
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
-
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>)
-
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
-
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new(account_id: id)</tt>)
-
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save; b</tt>)
-
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save!; b</tt>)
-
# * <tt>Account#reload_beneficiary</tt>
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific record or customize the generated query
-
# when you access the associated object.
-
#
-
# Scope examples:
-
# has_one :author, -> { where(comment_id: 1) }
-
# has_one :employer, -> { joins(:company) }
-
# has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }
-
#
-
# === Options
-
#
-
# The declaration can also include an +options+ hash to specialize the behavior of the association.
-
#
-
# Options are:
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:dependent]
-
# Controls what happens to the associated object when
-
# its owner is destroyed:
-
#
-
# * <tt>nil</tt> do nothing (default).
-
# * <tt>:destroy</tt> causes the associated object to also be destroyed
-
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
-
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
-
# on polymorphic associations. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there is an associated record
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
-
#
-
# Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association
-
# will use "person_id" as the default <tt>:foreign_key</tt>.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option.
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the polymorphic association
-
# specified on "as" option with a "_type" suffix. So a class that defines a
-
# <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the
-
# default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key used for the association. By default this is +id+.
-
# [:as]
-
# Specifies a polymorphic interface (See #belongs_to).
-
# [:through]
-
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection. You can only use a <tt>:through</tt> query through a #has_one
-
# or #belongs_to association on the join model.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option.
-
# [:source]
-
# Specifies the source association name used by #has_one <tt>:through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_one :favorite, through: :favorites</tt> will look for a
-
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source
-
# association is a polymorphic #belongs_to.
-
# [:validate]
-
# When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
-
# If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
-
# <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the #belongs_to association on the associated object
-
# that is the inverse of this #has_one association.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
# [:required]
-
# When set to +true+, the association will also have its presence validated.
-
# This will validate the association itself, not the id. You can use
-
# +:inverse_of+ to avoid an extra query during validation.
-
# [:strict_loading]
-
# Enforces strict loading every time the associated record is loaded through this association.
-
#
-
# Option examples:
-
# has_one :credit_card, dependent: :destroy # destroys the associated credit card
-
# has_one :credit_card, dependent: :nullify # updates the associated records foreign
-
# # key value to NULL rather than destroying it
-
# has_one :last_comment, -> { order('posted_on') }, class_name: "Comment"
-
# has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person"
-
# has_one :attachment, as: :attachable
-
# has_one :boss, -> { readonly }
-
# has_one :club, through: :membership
-
# has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
-
# has_one :credit_card, required: true
-
# has_one :credit_card, strict_loading: true
-
3
def has_one(name, scope = nil, **options)
-
423
reflection = Builder::HasOne.build(self, name, scope, options)
-
405
Reflection.add_reflection self, name, reflection
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if this class contains the foreign key. If the other class contains the foreign key,
-
# then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use #has_one and when to use #belongs_to.
-
#
-
# Methods will be added for retrieval and query for a single associated object, for which
-
# this object holds an id:
-
#
-
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
-
#
-
# [association]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
-
# No modification or deletion of existing records takes place.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid
-
# if the record is invalid.
-
# [reload_association]
-
# Returns the associated object, forcing a database read.
-
#
-
# === Example
-
#
-
# A Post class declares <tt>belongs_to :author</tt>, which will add:
-
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
-
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
-
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
-
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
-
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
-
# * <tt>Post#reload_author</tt>
-
# The declaration can also include an +options+ hash to specialize the behavior of the association.
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific record or customize the generated query
-
# when you access the associated object.
-
#
-
# Scope examples:
-
# belongs_to :firm, -> { where(id: 2) }
-
# belongs_to :user, -> { joins(:friends) }
-
# belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
-
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
-
# <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
-
# of "favorite_person_id".
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option.
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the association with a "_type"
-
# suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt>
-
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key of associated object used for the association.
-
# By default this is +id+.
-
# [:dependent]
-
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
-
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
-
# This option should not be specified when #belongs_to is used in conjunction with
-
# a #has_many relationship on another class because of the potential to leave
-
# orphaned records behind.
-
# [:counter_cache]
-
# Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter
-
# and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this
-
# class is created and decremented when it's destroyed. This requires that a column
-
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
-
# is used on the associate class (such as a Post class) - that is the migration for
-
# <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will
-
# return the count cached, see note below). You can also specify a custom counter
-
# cache column by providing a column name instead of a +true+/+false+ value to this
-
# option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
-
# Note: Specifying a counter cache will add it to that model's list of readonly attributes
-
# using +attr_readonly+.
-
# [:polymorphic]
-
# Specify this association is a polymorphic association by passing +true+.
-
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
-
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
-
# [:validate]
-
# When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
-
# If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for
-
# sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:touch]
-
# If true, the associated object will be touched (the updated_at/on attributes set to current time)
-
# when this record is either saved or destroyed. If you specify a symbol, that attribute
-
# will be updated with the current time in addition to the updated_at/on attribute.
-
# Please note that with touching no validation is performed and only the +after_touch+,
-
# +after_commit+ and +after_rollback+ callbacks are executed.
-
# [:inverse_of]
-
# Specifies the name of the #has_one or #has_many association on the associated
-
# object that is the inverse of this #belongs_to association.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
# [:optional]
-
# When set to +true+, the association will not have its presence validated.
-
# [:required]
-
# When set to +true+, the association will also have its presence validated.
-
# This will validate the association itself, not the id. You can use
-
# +:inverse_of+ to avoid an extra query during validation.
-
# NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
-
# you don't want to have association presence validated, use <tt>optional: true</tt>.
-
# [:default]
-
# Provide a callable (i.e. proc or lambda) to specify that the association should
-
# be initialized with a particular record before validation.
-
# [:strict_loading]
-
# Enforces strict loading every time the associated record is loaded through this association.
-
#
-
# Option examples:
-
# belongs_to :firm, foreign_key: "client_of"
-
# belongs_to :person, primary_key: "name", foreign_key: "person_name"
-
# belongs_to :author, class_name: "Person", foreign_key: "author_id"
-
# belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
-
# class_name: "Coupon", foreign_key: "coupon_id"
-
# belongs_to :attachable, polymorphic: true
-
# belongs_to :project, -> { readonly }
-
# belongs_to :post, counter_cache: true
-
# belongs_to :comment, touch: true
-
# belongs_to :company, touch: :employees_last_updated_at
-
# belongs_to :user, optional: true
-
# belongs_to :account, default: -> { company.account }
-
# belongs_to :account, strict_loading: true
-
3
def belongs_to(name, scope = nil, **options)
-
1311
reflection = Builder::BelongsTo.build(self, name, scope, options)
-
1293
Reflection.add_reflection self, name, reflection
-
end
-
-
# Specifies a many-to-many relationship with another class. This associates two classes via an
-
# intermediate join table. Unless the join table is explicitly specified as an option, it is
-
# guessed using the lexical order of the class names. So a join between Developer and Project
-
# will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
-
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
-
# means that if the strings are of different lengths, and the strings are equal when compared
-
# up to the shortest length, then the longer string is considered of higher
-
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
-
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
-
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
-
# custom <tt>:join_table</tt> option if you need to.
-
# If your tables share a common prefix, it will only appear once at the beginning. For example,
-
# the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products".
-
#
-
# The join table should not have a primary key or a model associated with it. You must manually generate the
-
# join table with a migration such as this:
-
#
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[6.0]
-
# def change
-
# create_join_table :developers, :projects
-
# end
-
# end
-
#
-
# It's also a good idea to add indexes to each of those columns to speed up the joins process.
-
# However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
-
# uses one index per table during the lookup.
-
#
-
# Adds the following methods for retrieval and query:
-
#
-
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
-
#
-
# [collection]
-
# Returns a Relation of all the associated objects.
-
# An empty Relation is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by creating associations in the join table
-
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
-
# Note that this operation instantly fires update SQL without waiting for the save or update call on the
-
# parent object, unless the parent object is a new record.
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by removing their associations from the join table.
-
# This does not destroy the objects.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option.
-
# This does not destroy the objects.
-
# [collection=objects]
-
# Replaces the collection's content by deleting and adding objects as appropriate.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids.
-
# [collection_singular_ids=ids]
-
# Replace the collection by the objects identified by the primary keys in +ids+.
-
# [collection.clear]
-
# Removes every object from the collection. This does not destroy the objects.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(id)]
-
# Finds an associated object responding to the +id+ and that
-
# meets the condition that it has to be associated with this object.
-
# Uses the same rules as ActiveRecord::FinderMethods#find.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as ActiveRecord::FinderMethods#exists?.
-
# [collection.build(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through the join table, and that has already been
-
# saved (if it passed the validation).
-
# [collection.reload]
-
# Returns a Relation of all of the associated objects, forcing a database read.
-
# An empty Relation is returned if none are found.
-
#
-
# === Example
-
#
-
# A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
-
# * <tt>Developer#projects</tt>
-
# * <tt>Developer#projects<<</tt>
-
# * <tt>Developer#projects.delete</tt>
-
# * <tt>Developer#projects.destroy</tt>
-
# * <tt>Developer#projects=</tt>
-
# * <tt>Developer#project_ids</tt>
-
# * <tt>Developer#project_ids=</tt>
-
# * <tt>Developer#projects.clear</tt>
-
# * <tt>Developer#projects.empty?</tt>
-
# * <tt>Developer#projects.size</tt>
-
# * <tt>Developer#projects.find(id)</tt>
-
# * <tt>Developer#projects.exists?(...)</tt>
-
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new(developer_id: id)</tt>)
-
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new(developer_id: id); c.save; c</tt>)
-
# * <tt>Developer#projects.reload</tt>
-
# The declaration may include an +options+ hash to specialize the behavior of the association.
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific set of records or customize the generated
-
# query when you access the associated collection.
-
#
-
# Scope examples:
-
# has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
-
# has_and_belongs_to_many :categories, ->(post) {
-
# where("default_category = ?", post.default_category)
-
# }
-
#
-
# === Extensions
-
#
-
# The +extension+ argument allows you to pass a block into a
-
# has_and_belongs_to_many association. This is useful for adding new
-
# finders, creators and other factory-type methods to be used as part of
-
# the association.
-
#
-
# Extension examples:
-
# has_and_belongs_to_many :contractors do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
-
# Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
-
# [:join_table]
-
# Specify the name of the join table if the default based on lexical order isn't what you want.
-
# <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method
-
# MUST be declared underneath any #has_and_belongs_to_many declaration in order to work.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes
-
# a #has_and_belongs_to_many association to Project will use "person_id" as the
-
# default <tt>:foreign_key</tt>.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option.
-
# [:association_foreign_key]
-
# Specify the foreign key used for the association on the receiving side of the association.
-
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
-
# So if a Person class makes a #has_and_belongs_to_many association to Project,
-
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
-
# [:validate]
-
# When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
-
# If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records.
-
#
-
# Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
-
# <tt>:autosave</tt> to <tt>true</tt>.
-
# [:strict_loading]
-
# Enforces strict loading every time an associated record is loaded through this association.
-
#
-
# Option examples:
-
# has_and_belongs_to_many :projects
-
# has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
-
# has_and_belongs_to_many :nations, class_name: "Country"
-
# has_and_belongs_to_many :categories, join_table: "prods_cats"
-
# has_and_belongs_to_many :categories, -> { readonly }
-
# has_and_belongs_to_many :categories, strict_loading: true
-
3
def has_and_belongs_to_many(name, scope = nil, **options, &extension)
-
242
habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
-
-
242
builder = Builder::HasAndBelongsToMany.new name, self, options
-
-
242
join_model = builder.through_model
-
-
242
const_set join_model.name, join_model
-
242
private_constant join_model.name
-
-
242
middle_reflection = builder.middle_reflection join_model
-
-
242
Builder::HasMany.define_callbacks self, middle_reflection
-
242
Reflection.add_reflection self, middle_reflection.name, middle_reflection
-
242
middle_reflection.parent_reflection = habtm_reflection
-
-
242
include Module.new {
-
242
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def destroy_associations
-
association(:#{middle_reflection.name}).delete_all(:delete_all)
-
association(:#{name}).reset
-
super
-
end
-
RUBY
-
}
-
-
242
hm_options = {}
-
242
hm_options[:through] = middle_reflection.name
-
242
hm_options[:source] = join_model.right_reflection.name
-
-
242
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
-
2420
hm_options[k] = options[k] if options.key? k
-
end
-
-
242
has_many name, scope, **hm_options, &extension
-
242
_reflections[name.to_s].parent_reflection = habtm_reflection
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/conversions"
-
-
3
module ActiveRecord
-
3
module Associations
-
# Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
-
3
class AliasTracker # :nodoc:
-
3
def self.create(connection, initial_table, joins, aliases = nil)
-
13139
if joins.empty?
-
13010
aliases ||= Hash.new(0)
-
129
elsif aliases
-
6
default_proc = aliases.default_proc || proc { 0 }
-
6
aliases.default_proc = proc { |h, k|
-
h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
-
}
-
else
-
123
aliases = Hash.new { |h, k|
-
156
h[k] = initial_count_for(connection, k, joins)
-
}
-
end
-
13139
aliases[initial_table] = 1
-
13139
new(connection, aliases)
-
end
-
-
3
def self.initial_count_for(connection, name, table_joins)
-
156
quoted_name = nil
-
-
156
counts = table_joins.map do |join|
-
156
if join.is_a?(Arel::Nodes::StringJoin)
-
# quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase
-
42
quoted_name ||= connection.quote_table_name(name)
-
-
# Table names + table aliases
-
join.left.scan(
-
/JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
-
42
).size
-
114
elsif join.is_a?(Arel::Nodes::Join)
-
114
join.left.name == name ? 1 : 0
-
else
-
raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
-
end
-
end
-
-
156
counts.sum
-
end
-
-
# table_joins is an array of arel joins which might conflict with the aliases we assign here
-
3
def initialize(connection, aliases)
-
13139
@aliases = aliases
-
13139
@connection = connection
-
end
-
-
3
def aliased_table_for(arel_table)
-
6089
if aliases[arel_table.name] == 0
-
# If it's zero, we can have our table_name
-
5603
aliases[arel_table.name] = 1
-
else
-
# Otherwise, we need to use an alias
-
486
aliased_name = @connection.table_alias_for(yield)
-
-
# Update the count
-
486
count = aliases[aliased_name] += 1
-
-
486
aliased_name = "#{truncate(aliased_name)}_#{count}" if count > 1
-
-
486
arel_table = arel_table.alias(aliased_name)
-
end
-
-
6089
arel_table
-
end
-
-
3
attr_reader :aliases
-
-
3
private
-
3
def truncate(name)
-
51
name.slice(0, @connection.table_alias_length - 2)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Associations
-
#
-
# This is the root class of all associations ('+ Foo' signifies an included module Foo):
-
#
-
# Association
-
# SingularAssociation
-
# HasOneAssociation + ForeignAssociation
-
# HasOneThroughAssociation + ThroughAssociation
-
# BelongsToAssociation
-
# BelongsToPolymorphicAssociation
-
# CollectionAssociation
-
# HasManyAssociation + ForeignAssociation
-
# HasManyThroughAssociation + ThroughAssociation
-
#
-
# Associations in Active Record are middlemen between the object that
-
# holds the association, known as the <tt>owner</tt>, and the associated
-
# result set, known as the <tt>target</tt>. Association metadata is available in
-
# <tt>reflection</tt>, which is an instance of <tt>ActiveRecord::Reflection::AssociationReflection</tt>.
-
#
-
# For example, given
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :posts
-
# end
-
#
-
# blog = Blog.first
-
#
-
# The association of <tt>blog.posts</tt> has the object +blog+ as its
-
# <tt>owner</tt>, the collection of its posts as <tt>target</tt>, and
-
# the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
-
3
class Association #:nodoc:
-
3
attr_reader :owner, :target, :reflection
-
-
3
delegate :options, to: :reflection
-
-
3
def initialize(owner, reflection)
-
227995
reflection.check_validity!
-
-
227968
@owner, @reflection = owner, reflection
-
-
227968
reset
-
227968
reset_scope
-
end
-
-
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
-
3
def reset
-
232254
@loaded = false
-
232254
@target = nil
-
232254
@stale_state = nil
-
232254
@inversed = false
-
end
-
-
3
def reset_negative_cache # :nodoc:
-
57
reset if loaded? && target.nil?
-
end
-
-
# Reloads the \target and returns +self+ on success.
-
# The QueryCache is cleared if +force+ is true.
-
3
def reload(force = false)
-
2395
klass.connection.clear_query_cache if force && klass
-
2395
reset
-
2395
reset_scope
-
2395
load_target
-
2377
self unless target.nil?
-
end
-
-
# Has the \target been already \loaded?
-
3
def loaded?
-
68652
@loaded
-
end
-
-
# Asserts the \target has been loaded setting the \loaded flag to +true+.
-
3
def loaded!
-
233189
@loaded = true
-
233189
@stale_state = stale_state
-
233186
@inversed = false
-
end
-
-
# The target is stale if the target no longer points to the record(s) that the
-
# relevant foreign_key(s) refers to. If stale, the association accessor method
-
# on the owner will reload the target. It's up to subclasses to implement the
-
# stale_state method if relevant.
-
#
-
# Note that if the target has not been loaded, it is not considered stale.
-
3
def stale_target?
-
26190
!@inversed && loaded? && @stale_state != stale_state
-
end
-
-
# Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
-
3
def target=(target)
-
217452
@target = target
-
217452
loaded!
-
end
-
-
3
def scope
-
14563
if (scope = klass.current_scope) && scope.try(:proxy_association) == self
-
72
scope.spawn
-
else
-
14491
target_scope.merge!(association_scope)
-
end
-
end
-
-
3
def reset_scope
-
231846
@association_scope = nil
-
end
-
-
# Set the inverse association, if possible
-
3
def set_inverse_instance(record)
-
22978
if inverse = inverse_association_for(record)
-
8617
inverse.inversed_from(owner)
-
end
-
22972
record
-
end
-
-
3
def set_inverse_instance_from_queries(record)
-
4483
if inverse = inverse_association_for(record)
-
1348
inverse.inversed_from_queries(owner)
-
end
-
4483
record
-
end
-
-
# Remove the inverse association, if possible
-
3
def remove_inverse_instance(record)
-
80
if inverse = inverse_association_for(record)
-
57
inverse.inversed_from(nil)
-
end
-
end
-
-
3
def inversed_from(record)
-
10022
self.target = record
-
10022
@inversed = !!record
-
end
-
3
alias :inversed_from_queries :inversed_from
-
-
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
-
# polymorphic_type field on the owner.
-
3
def klass
-
544518
reflection.klass
-
end
-
-
3
def extensions
-
6730
extensions = klass.default_extensions | reflection.extensions
-
-
6730
if reflection.scope
-
1640
extensions |= reflection.scope_for(klass.unscoped, owner).extensions
-
end
-
-
6730
extensions
-
end
-
-
# Loads the \target if needed and returns it.
-
#
-
# This method is abstract in the sense that it relies on +find_target+,
-
# which is expected to be provided by descendants.
-
#
-
# If the \target is already \loaded it is just returned. Thus, you can call
-
# +load_target+ unconditionally to get the \target.
-
#
-
# ActiveRecord::RecordNotFound is rescued within the method, and it is
-
# not reraised. The proxy is \reset and +nil+ is the return value.
-
3
def load_target
-
7905
@target = find_target if (@stale_state && stale_target?) || find_target?
-
-
7890
loaded! unless loaded?
-
7887
target
-
rescue ActiveRecord::RecordNotFound
-
reset
-
end
-
-
# We can't dump @reflection and @through_reflection since it contains the scope proc
-
3
def marshal_dump
-
339
ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
-
39
[@reflection.name, ivars]
-
end
-
-
3
def marshal_load(data)
-
46
reflection_name, ivars = data
-
402
ivars.each { |name, val| instance_variable_set(name, val) }
-
46
@reflection = @owner.class._reflect_on_association(reflection_name)
-
end
-
-
3
def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
-
4271
except_from_scope_attributes ||= {}
-
4271
skip_assign = [reflection.foreign_key, reflection.type].compact
-
4271
assigned_keys = record.changed_attribute_names_to_save
-
4271
assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
-
4271
attributes = scope_for_create.except!(*(assigned_keys - skip_assign))
-
4271
record.send(:_assign_attributes, attributes) if attributes.any?
-
4268
set_inverse_instance(record)
-
end
-
-
3
def create(attributes = nil, &block)
-
1090
_create_record(attributes, &block)
-
end
-
-
3
def create!(attributes = nil, &block)
-
967
_create_record(attributes, true, &block)
-
end
-
-
3
private
-
3
def find_target
-
4943
if owner.strict_loading?
-
27
raise StrictLoadingViolationError, "#{owner.class} is marked as strict_loading and #{klass} cannot be lazily loaded."
-
end
-
-
4916
if reflection.strict_loading?
-
12
raise StrictLoadingViolationError, "The #{reflection.name} association is marked as strict_loading and cannot be lazily loaded."
-
end
-
-
4904
scope = self.scope
-
4904
return scope.to_a if skip_statement_cache?(scope)
-
-
3218
sc = reflection.association_scope_cache(klass, owner) do |params|
-
1484
as = AssociationScope.create { params.bind }
-
700
target_scope.merge!(as.scope(self))
-
end
-
-
3218
binds = AssociationScope.get_bind_values(owner, reflection.chain)
-
6387
sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) }
-
end
-
-
# The scope for this association.
-
#
-
# Note that the association_scope is merged into the target_scope only when the
-
# scope method is called. This is because at that point the call may be surrounded
-
# by scope.scoping { ... } or unscoped { ... } etc, which affects the scope which
-
# actually gets built.
-
3
def association_scope
-
20390
if klass
-
20390
@association_scope ||= AssociationScope.scope(self)
-
end
-
end
-
-
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
-
# through association's scope)
-
3
def target_scope
-
15191
AssociationRelation.create(klass, self).merge!(klass.scope_for_association)
-
end
-
-
3
def scope_for_create
-
4271
scope.scope_for_create
-
end
-
-
3
def find_target?
-
10212
!loaded? && (!owner.new_record? || foreign_key_present?) && klass
-
end
-
-
# Returns true if there is a foreign key present on the owner which
-
# references the target. This is used to determine whether we can load
-
# the target if the owner is currently a new record (and therefore
-
# without a key). If the owner is a new record then foreign_key must
-
# be present in order to load target.
-
#
-
# Currently implemented by belongs_to (vanilla and polymorphic) and
-
# has_one/has_many :through associations which go through a belongs_to.
-
3
def foreign_key_present?
-
false
-
end
-
-
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
-
# the kind of the class of the associated objects. Meant to be used as
-
# a sanity check when you are about to assign an associated record.
-
3
def raise_on_type_mismatch!(record)
-
5210
unless record.is_a?(reflection.klass)
-
45
fresh_class = reflection.class_name.safe_constantize
-
45
unless fresh_class && record.is_a?(fresh_class)
-
42
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
-
"got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
-
42
raise ActiveRecord::AssociationTypeMismatch, message
-
end
-
end
-
end
-
-
3
def inverse_association_for(record)
-
27541
if invertible_for?(record)
-
10022
record.association(inverse_reflection_for(record).name)
-
end
-
end
-
-
# Can be redefined by subclasses, notably polymorphic belongs_to
-
# The record parameter is necessary to support polymorphic inverses as we must check for
-
# the association in the specific class of the record.
-
3
def inverse_reflection_for(record)
-
31972
reflection.inverse_of
-
end
-
-
# Returns true if inverse association on the given record needs to be set.
-
# This method is redefined by subclasses.
-
3
def invertible_for?(record)
-
17464
foreign_key_for?(record) && inverse_reflection_for(record)
-
end
-
-
# Returns true if record contains the foreign_key
-
3
def foreign_key_for?(record)
-
17464
record._has_attribute?(reflection.foreign_key)
-
end
-
-
# This should be implemented to return the values of the relevant key(s) on the owner,
-
# so that when stale_state is different from the value stored on the last find_target,
-
# the target is stale.
-
#
-
# This is only relevant to certain associations, which is why it returns +nil+ by default.
-
3
def stale_state
-
end
-
-
3
def build_record(attributes)
-
4292
reflection.build_association(attributes) do |record|
-
4265
initialize_attributes(record, attributes)
-
4262
yield(record) if block_given?
-
end
-
end
-
-
# Returns true if statement cache should be skipped on the association reader.
-
3
def skip_statement_cache?(scope)
-
4904
reflection.has_scope? ||
-
scope.eager_loading? ||
-
klass.scope_attributes? ||
-
reflection.source_reflection.active_record.default_scopes.any?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class AssociationScope #:nodoc:
-
3
def self.scope(association)
-
10325
INSTANCE.scope(association)
-
end
-
-
3
def self.create(&block)
-
11862
block ||= lambda { |val| val }
-
703
new(block)
-
end
-
-
3
def initialize(value_transformation)
-
703
@value_transformation = value_transformation
-
end
-
-
3
INSTANCE = create
-
-
3
def scope(association)
-
11025
klass = association.klass
-
11025
reflection = association.reflection
-
11025
scope = klass.unscoped
-
11025
owner = association.owner
-
11025
chain = get_chain(reflection, association, scope.alias_tracker)
-
-
11025
scope.extending! reflection.extensions
-
11025
scope = add_constraints(scope, owner, chain)
-
11025
scope.limit!(1) unless reflection.collection?
-
11025
scope
-
end
-
-
3
def self.get_bind_values(owner, chain)
-
3218
binds = []
-
3218
last_reflection = chain.last
-
-
3218
binds << last_reflection.join_id_for(owner)
-
3218
if last_reflection.type
-
246
binds << owner.class.polymorphic_name
-
end
-
-
3218
chain.each_cons(2).each do |reflection, next_reflection|
-
672
if reflection.type
-
15
binds << next_reflection.klass.polymorphic_name
-
end
-
end
-
3218
binds
-
end
-
-
3
private
-
3
attr_reader :value_transformation
-
-
3
def join(table, constraint)
-
2950
Arel::Nodes::LeadingJoin.new(table, Arel::Nodes::On.new(constraint))
-
end
-
-
3
def last_chain_scope(scope, reflection, owner)
-
11025
primary_key = reflection.join_primary_key
-
11025
foreign_key = reflection.join_foreign_key
-
-
11025
table = reflection.aliased_table
-
11025
value = transform_value(owner[foreign_key])
-
11025
scope = apply_scope(scope, table, primary_key, value)
-
-
11025
if reflection.type
-
855
polymorphic_type = transform_value(owner.class.polymorphic_name)
-
855
scope = apply_scope(scope, table, reflection.type, polymorphic_type)
-
end
-
-
11025
scope
-
end
-
-
3
def transform_value(value)
-
11943
value_transformation.call(value)
-
end
-
-
3
def next_chain_scope(scope, reflection, next_reflection)
-
2950
primary_key = reflection.join_primary_key
-
2950
foreign_key = reflection.join_foreign_key
-
-
2950
table = reflection.aliased_table
-
2950
foreign_table = next_reflection.aliased_table
-
2950
constraint = table[primary_key].eq(foreign_table[foreign_key])
-
-
2950
if reflection.type
-
63
value = transform_value(next_reflection.klass.polymorphic_name)
-
63
scope = apply_scope(scope, table, reflection.type, value)
-
end
-
-
2950
scope.joins!(join(foreign_table, constraint))
-
end
-
-
3
class ReflectionProxy < SimpleDelegator # :nodoc:
-
3
attr_reader :aliased_table
-
-
3
def initialize(reflection, aliased_table)
-
2950
super(reflection)
-
2950
@aliased_table = aliased_table
-
end
-
-
3
def all_includes; nil; end
-
end
-
-
3
def get_chain(reflection, association, tracker)
-
11025
name = reflection.name
-
11025
chain = [Reflection::RuntimeReflection.new(reflection, association)]
-
11025
reflection.chain.drop(1).each do |refl|
-
2950
aliased_table = tracker.aliased_table_for(refl.klass.arel_table) do
-
39
refl.alias_candidate(name)
-
end
-
2950
chain << ReflectionProxy.new(refl, aliased_table)
-
end
-
11025
chain
-
end
-
-
3
def add_constraints(scope, owner, chain)
-
11025
scope = last_chain_scope(scope, chain.last, owner)
-
-
11025
chain.each_cons(2) do |reflection, next_reflection|
-
2950
scope = next_chain_scope(scope, reflection, next_reflection)
-
end
-
-
11025
chain_head = chain.first
-
11025
chain.reverse_each do |reflection|
-
# Exclude the scope of the association itself, because that
-
# was already merged in the #scope method.
-
13975
reflection.constraints.each do |scope_chain_item|
-
2675
item = eval_scope(reflection, scope_chain_item, owner)
-
-
2675
if scope_chain_item == chain_head.scope
-
2132
scope.merge! item.except(:where, :includes, :unscope, :order)
-
543
elsif !item.references_values.empty?
-
36
join_dependency = item.construct_join_dependency(
-
item.eager_load_values | item.includes_values, Arel::Nodes::OuterJoin
-
)
-
36
scope.joins!(*item.joins_values, join_dependency)
-
36
scope.left_outer_joins!(*item.left_outer_joins_values)
-
end
-
-
2675
reflection.all_includes do
-
2429
scope.includes_values |= item.includes_values
-
end
-
-
2675
scope.unscope!(*item.unscope_values)
-
2675
scope.where_clause += item.where_clause
-
2675
scope.order_values = item.order_values | scope.order_values
-
end
-
end
-
-
11025
scope
-
end
-
-
3
def apply_scope(scope, table, key, value)
-
11943
if scope.table == table
-
8861
scope.where!(key => value)
-
else
-
3082
scope.where!(table.name => { key => value })
-
end
-
end
-
-
3
def eval_scope(reflection, scope, owner)
-
2675
relation = reflection.build_scope(reflection.aliased_table)
-
2675
relation.instance_exec(owner, &scope) || relation
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Belongs To Association
-
3
class BelongsToAssociation < SingularAssociation #:nodoc:
-
3
def handle_dependency
-
102
return unless load_target
-
-
45
case options[:dependent]
-
when :destroy
-
33
target.destroy
-
33
raise ActiveRecord::Rollback unless target.destroyed?
-
else
-
12
target.send(options[:dependent])
-
end
-
end
-
-
3
def inversed_from(record)
-
7872
replace_keys(record)
-
7872
super
-
end
-
-
3
def default(&block)
-
15
writer(owner.instance_exec(&block)) if reader.nil?
-
end
-
-
3
def reset
-
213445
super
-
213445
@updated = false
-
end
-
-
3
def updated?
-
3863
@updated
-
end
-
-
3
def decrement_counters
-
131
update_counters(-1)
-
end
-
-
3
def increment_counters
-
950
update_counters(1)
-
end
-
-
3
def decrement_counters_before_last_save
-
114
if reflection.polymorphic?
-
27
model_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize
-
else
-
87
model_was = klass
-
end
-
-
114
foreign_key_was = owner.attribute_before_last_save(reflection.foreign_key)
-
-
114
if foreign_key_was && model_was < ActiveRecord::Base
-
51
update_counters_via_scope(model_was, foreign_key_was, -1)
-
end
-
end
-
-
3
def target_changed?
-
527
owner.saved_change_to_attribute?(reflection.foreign_key)
-
end
-
-
3
private
-
3
def replace(record)
-
2248
if record
-
2206
raise_on_type_mismatch!(record)
-
2197
set_inverse_instance(record)
-
2191
@updated = true
-
end
-
-
2233
replace_keys(record)
-
-
2227
self.target = record
-
end
-
-
3
def update_counters(by)
-
1081
if require_counter_update? && foreign_key_present?
-
593
if target && !stale_target?
-
345
target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
-
else
-
248
update_counters_via_scope(klass, owner._read_attribute(reflection.foreign_key), by)
-
end
-
end
-
end
-
-
3
def update_counters_via_scope(klass, foreign_key, by)
-
299
scope = klass.unscoped.where!(primary_key(klass) => foreign_key)
-
299
scope.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
-
end
-
-
3
def find_target?
-
5822
!loaded? && foreign_key_present? && klass
-
end
-
-
3
def require_counter_update?
-
1081
reflection.counter_cache_column && owner.persisted?
-
end
-
-
3
def replace_keys(record)
-
10105
owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record.class)) : nil
-
end
-
-
3
def primary_key(klass)
-
10305
reflection.association_primary_key(klass)
-
end
-
-
3
def foreign_key_present?
-
2637
owner._read_attribute(reflection.foreign_key)
-
end
-
-
3
def invertible_for?(record)
-
5482
inverse = inverse_reflection_for(record)
-
5476
inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing)
-
end
-
-
3
def stale_state
-
222757
result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
-
222751
result && result.to_s
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Belongs To Polymorphic Association
-
3
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
-
3
def klass
-
3393
type = owner[reflection.foreign_type]
-
3393
type.presence && owner.class.polymorphic_class_for(type)
-
end
-
-
3
def target_changed?
-
44
super || owner.saved_change_to_attribute?(reflection.foreign_type)
-
end
-
-
3
private
-
3
def replace_keys(record)
-
909
super
-
909
owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
-
end
-
-
3
def inverse_reflection_for(record)
-
870
reflection.polymorphic_inverse_of(record.class)
-
end
-
-
3
def raise_on_type_mismatch!(record)
-
# A polymorphic association cannot have a type mismatch, by definition
-
end
-
-
3
def stale_state
-
3243
foreign_key = super
-
3243
foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# This is the parent Association class which defines the variables
-
# used by all associations.
-
#
-
# The hierarchy is defined as follows:
-
# Association
-
# - SingularAssociation
-
# - BelongsToAssociation
-
# - HasOneAssociation
-
# - CollectionAssociation
-
# - HasManyAssociation
-
-
3
module ActiveRecord::Associations::Builder # :nodoc:
-
3
class Association #:nodoc:
-
3
class << self
-
3
attr_accessor :extensions
-
end
-
3
self.extensions = []
-
-
3
VALID_OPTIONS = [
-
:class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading
-
].freeze # :nodoc:
-
-
3
def self.build(model, name, scope, options, &block)
-
3399
if model.dangerous_attribute_method?(name)
-
36
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
-
"this will conflict with a method #{name} already defined by Active Record. " \
-
"Please choose a different association name."
-
end
-
-
3363
reflection = create_reflection(model, name, scope, options, &block)
-
3351
define_accessors model, reflection
-
3351
define_callbacks model, reflection
-
3348
define_validations model, reflection
-
3348
reflection
-
end
-
-
3
def self.create_reflection(model, name, scope, options, &block)
-
3605
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
-
-
3602
validate_options(options)
-
-
3593
extension = define_extensions(model, name, &block)
-
3593
options[:extend] = [*options[:extend], extension] if extension
-
-
3593
scope = build_scope(scope)
-
-
3593
ActiveRecord::Reflection.create(macro, name, scope, options, model)
-
end
-
-
3
def self.build_scope(scope)
-
3593
if scope && scope.arity == 0
-
5713
proc { instance_exec(&scope) }
-
else
-
3077
scope
-
end
-
end
-
-
3
def self.macro
-
raise NotImplementedError
-
end
-
-
3
def self.valid_options(options)
-
3602
VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
-
end
-
-
3
def self.validate_options(options)
-
3602
options.assert_valid_keys(valid_options(options))
-
end
-
-
3
def self.define_extensions(model, name)
-
end
-
-
3
def self.define_callbacks(model, reflection)
-
3593
if dependent = reflection.options[:dependent]
-
225
check_dependent_options(dependent)
-
222
add_destroy_callbacks(model, reflection)
-
end
-
-
3590
Association.extensions.each do |extension|
-
3590
extension.build model, reflection
-
end
-
end
-
-
# Defines the setter and getter methods for the association
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# end
-
#
-
# Post.first.comments and Post.first.comments= methods are defined by this method...
-
3
def self.define_accessors(model, reflection)
-
3351
mixin = model.generated_association_methods
-
3351
name = reflection.name
-
3351
define_readers(mixin, name)
-
3351
define_writers(mixin, name)
-
end
-
-
3
def self.define_readers(mixin, name)
-
3351
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}
-
association(:#{name}).reader
-
end
-
CODE
-
end
-
-
3
def self.define_writers(mixin, name)
-
3351
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}=(value)
-
association(:#{name}).writer(value)
-
end
-
CODE
-
end
-
-
3
def self.define_validations(model, reflection)
-
# noop
-
end
-
-
3
def self.valid_dependent_options
-
raise NotImplementedError
-
end
-
-
3
def self.check_dependent_options(dependent)
-
225
unless valid_dependent_options.include? dependent
-
3
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
-
end
-
end
-
-
3
def self.add_destroy_callbacks(model, reflection)
-
207
name = reflection.name
-
1156
model.before_destroy lambda { |o| o.association(name).handle_dependency }
-
end
-
-
3
private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
-
:define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
-
:valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord::Associations::Builder # :nodoc:
-
3
class BelongsTo < SingularAssociation #:nodoc:
-
3
def self.macro
-
1296
:belongs_to
-
end
-
-
3
def self.valid_options(options)
-
1296
valid = super + [:counter_cache, :optional, :default]
-
1296
valid += [:polymorphic, :foreign_type] if options[:polymorphic]
-
1296
valid
-
end
-
-
3
def self.valid_dependent_options
-
21
[:destroy, :delete]
-
end
-
-
3
def self.define_callbacks(model, reflection)
-
1296
super
-
1293
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
-
1293
add_touch_callbacks(model, reflection) if reflection.options[:touch]
-
1293
add_default_callbacks(model, reflection) if reflection.options[:default]
-
end
-
-
3
def self.add_counter_cache_callbacks(model, reflection)
-
57
cache_column = reflection.counter_cache_column
-
-
57
model.after_update lambda { |record|
-
377
association = association(reflection.name)
-
-
377
if association.target_changed?
-
114
association.increment_counters
-
114
association.decrement_counters_before_last_save
-
end
-
}
-
-
57
klass = reflection.class_name.safe_constantize
-
57
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
-
end
-
-
3
def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
-
636
old_foreign_id = changes[foreign_key] && changes[foreign_key].first
-
-
636
if old_foreign_id
-
24
association = o.association(name)
-
24
reflection = association.reflection
-
24
if reflection.polymorphic?
-
6
foreign_type = reflection.foreign_type
-
6
klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
-
6
klass = klass.constantize
-
else
-
18
klass = association.klass
-
end
-
24
primary_key = reflection.association_primary_key(klass)
-
24
old_record = klass.find_by(primary_key => old_foreign_id)
-
-
24
if old_record
-
21
if touch != true
-
old_record.send(touch_method, touch)
-
else
-
21
old_record.send(touch_method)
-
end
-
end
-
end
-
-
636
record = o.send name
-
636
if record && record.persisted?
-
387
if touch != true
-
6
record.send(touch_method, touch)
-
else
-
381
record.send(touch_method)
-
end
-
end
-
end
-
-
3
def self.add_touch_callbacks(model, reflection)
-
48
foreign_key = reflection.foreign_key
-
48
name = reflection.name
-
48
touch = reflection.options[:touch]
-
-
222
callback = lambda { |changes_method| lambda { |record|
-
588
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
-
}}
-
-
48
if reflection.counter_cache_column
-
9
touch_callback = callback.(:saved_changes)
-
9
update_callback = lambda { |record|
-
150
instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
-
}
-
9
model.after_update update_callback, if: :saved_changes?
-
else
-
39
model.after_create callback.(:saved_changes), if: :saved_changes?
-
39
model.after_update callback.(:saved_changes), if: :saved_changes?
-
39
model.after_destroy callback.(:changes_to_save)
-
end
-
-
48
model.after_touch callback.(:changes_to_save)
-
end
-
-
3
def self.add_default_callbacks(model, reflection)
-
6
model.before_validation lambda { |o|
-
15
o.association(reflection.name).default(&reflection.options[:default])
-
}
-
end
-
-
3
def self.add_destroy_callbacks(model, reflection)
-
117
model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
-
end
-
-
3
def self.define_validations(model, reflection)
-
1293
if reflection.options.key?(:required)
-
490
reflection.options[:optional] = !reflection.options.delete(:required)
-
end
-
-
1293
if reflection.options[:optional].nil?
-
794
required = model.belongs_to_required_by_default
-
else
-
499
required = !reflection.options[:optional]
-
end
-
-
1293
super
-
-
1293
if required
-
18
model.validates_presence_of reflection.name, message: :required
-
end
-
end
-
-
3
private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
-
:add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/associations"
-
-
3
module ActiveRecord::Associations::Builder # :nodoc:
-
3
class CollectionAssociation < Association #:nodoc:
-
3
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
-
-
3
def self.valid_options(options)
-
1895
super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
-
end
-
-
3
def self.define_callbacks(model, reflection)
-
1892
super
-
1892
name = reflection.name
-
1892
options = reflection.options
-
1892
CALLBACKS.each { |callback_name|
-
7568
define_callback(model, callback_name, name, options)
-
}
-
end
-
-
3
def self.define_extensions(model, name, &block)
-
1898
if block_given?
-
30
extension_module_name = "#{name.to_s.camelize}AssociationExtension"
-
30
extension = Module.new(&block)
-
30
model.const_set(extension_module_name, extension)
-
end
-
end
-
-
3
def self.define_callback(model, callback_name, name, options)
-
7568
full_callback_name = "#{callback_name}_for_#{name}"
-
-
# TODO : why do i need method_defined? I think its because of the inheritance chain
-
7568
model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
-
7568
callbacks = Array(options[callback_name.to_sym]).map do |callback|
-
168
case callback
-
when Symbol
-
336
->(method, owner, record) { owner.send(callback, record) }
-
when Proc
-
330
->(method, owner, record) { callback.call(owner, record) }
-
else
-
->(method, owner, record) { callback.send(method, owner, record) }
-
end
-
end
-
7568
model.send "#{full_callback_name}=", callbacks
-
end
-
-
# Defines the setter and getter methods for the collection_singular_ids.
-
3
def self.define_readers(mixin, name)
-
1650
super
-
-
1650
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids
-
association(:#{name}).ids_reader
-
end
-
CODE
-
end
-
-
3
def self.define_writers(mixin, name)
-
1650
super
-
-
1650
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids=(ids)
-
association(:#{name}).ids_writer(ids)
-
end
-
CODE
-
end
-
-
3
private_class_method :valid_options, :define_callback, :define_extensions, :define_readers, :define_writers
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord::Associations::Builder # :nodoc:
-
3
class HasAndBelongsToMany # :nodoc:
-
3
attr_reader :lhs_model, :association_name, :options
-
-
3
def initialize(association_name, lhs_model, options)
-
242
@association_name = association_name
-
242
@lhs_model = lhs_model
-
242
@options = options
-
end
-
-
3
def through_model
-
242
join_model = Class.new(ActiveRecord::Base) {
-
242
class << self
-
242
attr_accessor :left_model
-
242
attr_accessor :name
-
242
attr_accessor :table_name_resolver
-
242
attr_accessor :left_reflection
-
242
attr_accessor :right_reflection
-
end
-
-
242
def self.table_name
-
# Table name needs to be resolved lazily
-
# because RHS class might not have been loaded
-
2881
@table_name ||= table_name_resolver.call
-
end
-
-
242
def self.compute_type(class_name)
-
190
left_model.compute_type class_name
-
end
-
-
242
def self.add_left_association(name, options)
-
242
belongs_to name, required: false, **options
-
242
self.left_reflection = _reflect_on_association(name)
-
end
-
-
242
def self.add_right_association(name, options)
-
242
rhs_name = name.to_s.singularize.to_sym
-
242
belongs_to rhs_name, required: false, **options
-
242
self.right_reflection = _reflect_on_association(rhs_name)
-
end
-
-
242
def self.retrieve_connection
-
5174
left_model.retrieve_connection
-
end
-
-
242
private
-
242
def self.suppress_composite_primary_key(pk)
-
159
pk unless pk.is_a?(Array)
-
end
-
}
-
-
242
join_model.name = "HABTM_#{association_name.to_s.camelize}"
-
438
join_model.table_name_resolver = -> { table_name }
-
242
join_model.left_model = lhs_model
-
-
242
join_model.add_left_association :left_side, anonymous_class: lhs_model
-
242
join_model.add_right_association association_name, belongs_to_options(options)
-
242
join_model
-
end
-
-
3
def middle_reflection(join_model)
-
242
middle_name = [lhs_model.name.downcase.pluralize,
-
association_name.to_s].sort.join("_").gsub("::", "_").to_sym
-
242
middle_options = middle_options join_model
-
-
242
HasMany.create_reflection(lhs_model,
-
middle_name,
-
nil,
-
middle_options)
-
end
-
-
3
private
-
3
def middle_options(join_model)
-
242
middle_options = {}
-
242
middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
-
242
if options.key? :foreign_key
-
54
middle_options[:foreign_key] = options[:foreign_key]
-
end
-
242
middle_options
-
end
-
-
3
def table_name
-
196
if options[:join_table]
-
75
options[:join_table].to_s
-
else
-
121
class_name = options.fetch(:class_name) {
-
58
association_name.to_s.camelize.singularize
-
}
-
121
klass = lhs_model.send(:compute_type, class_name.to_s)
-
121
[lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
-
end
-
end
-
-
3
def belongs_to_options(options)
-
242
rhs_options = {}
-
-
242
if options.key? :class_name
-
114
rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
-
114
rhs_options[:class_name] = options[:class_name]
-
end
-
-
242
if options.key? :association_foreign_key
-
45
rhs_options[:foreign_key] = options[:association_foreign_key]
-
end
-
-
242
rhs_options
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord::Associations::Builder # :nodoc:
-
3
class HasMany < CollectionAssociation #:nodoc:
-
3
def self.macro
-
1892
:has_many
-
end
-
-
3
def self.valid_options(options)
-
1895
valid = super + [:counter_cache, :join_table, :index_errors]
-
1895
valid += [:as, :foreign_type] if options[:as]
-
1895
valid += [:through, :source, :source_type] if options[:through]
-
1895
valid
-
end
-
-
3
def self.valid_dependent_options
-
153
[:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
-
end
-
-
3
private_class_method :macro, :valid_options, :valid_dependent_options
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord::Associations::Builder # :nodoc:
-
3
class HasOne < SingularAssociation #:nodoc:
-
3
def self.macro
-
405
:has_one
-
end
-
-
3
def self.valid_options(options)
-
411
valid = super
-
411
valid += [:as, :foreign_type] if options[:as]
-
411
valid += [:through, :source, :source_type] if options[:through]
-
411
valid
-
end
-
-
3
def self.valid_dependent_options
-
54
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
-
end
-
-
3
def self.define_callbacks(model, reflection)
-
405
super
-
405
add_touch_callbacks(model, reflection) if reflection.options[:touch]
-
end
-
-
3
def self.add_destroy_callbacks(model, reflection)
-
54
super unless reflection.options[:through]
-
end
-
-
3
def self.define_validations(model, reflection)
-
405
super
-
405
if reflection.options[:required]
-
6
model.validates_presence_of reflection.name, message: :required
-
end
-
end
-
-
3
def self.touch_record(record, name, touch)
-
69
instance = record.send(name)
-
-
69
if instance&.persisted?
-
21
touch != true ?
-
21
instance.touch(touch) : instance.touch
-
end
-
end
-
-
3
def self.add_touch_callbacks(model, reflection)
-
6
name = reflection.name
-
6
touch = reflection.options[:touch]
-
-
75
callback = -> (record) { HasOne.touch_record(record, name, touch) }
-
6
model.after_create callback, if: :saved_changes?
-
63
model.after_create_commit { association(name).reset_negative_cache }
-
6
model.after_update callback, if: :saved_changes?
-
6
model.after_destroy callback
-
6
model.after_touch callback
-
end
-
-
3
private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
-
:define_callbacks, :define_validations, :add_touch_callbacks
-
end
-
end
-
# frozen_string_literal: true
-
-
# This class is inherited by the has_one and belongs_to association classes
-
-
3
module ActiveRecord::Associations::Builder # :nodoc:
-
3
class SingularAssociation < Association #:nodoc:
-
3
def self.valid_options(options)
-
1707
super + [:required, :touch]
-
end
-
-
3
def self.define_accessors(model, reflection)
-
1701
super
-
1701
mixin = model.generated_association_methods
-
1701
name = reflection.name
-
-
1701
define_constructors(mixin, name) if reflection.constructable?
-
-
1701
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def reload_#{name}
-
association(:#{name}).force_reload_reader
-
end
-
CODE
-
end
-
-
# Defines the (build|create)_association methods for belongs_to or has_one association
-
3
def self.define_constructors(mixin, name)
-
1509
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def build_#{name}(*args, &block)
-
association(:#{name}).build(*args, &block)
-
end
-
-
def create_#{name}(*args, &block)
-
association(:#{name}).create(*args, &block)
-
end
-
-
def create_#{name}!(*args, &block)
-
association(:#{name}).create!(*args, &block)
-
end
-
CODE
-
end
-
-
3
private_class_method :valid_options, :define_accessors, :define_constructors
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Association Collection
-
#
-
# CollectionAssociation is an abstract class that provides common stuff to
-
# ease the implementation of association proxies that represent
-
# collections. See the class hierarchy in Association.
-
#
-
# CollectionAssociation:
-
# HasManyAssociation => has_many
-
# HasManyThroughAssociation + ThroughAssociation => has_many :through
-
#
-
# The CollectionAssociation class provides common methods to the collections
-
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
-
# the +:through association+ option.
-
#
-
# You need to be careful with assumptions regarding the target: The proxy
-
# does not fetch records from the database until it needs them, but new
-
# ones created with +build+ are added to the target. So, the target may be
-
# non-empty and still lack children waiting to be read from the database.
-
# If you look directly to the database you cannot assume that's the entire
-
# collection because new records may have been added to the target, etc.
-
#
-
# If you need to work on all current children, new and existing records,
-
# +load_target+ and the +loaded+ flag are your friends.
-
3
class CollectionAssociation < Association #:nodoc:
-
# Implements the reader method, e.g. foo.items for Foo.has_many :items
-
3
def reader
-
11483
if stale_target?
-
9
reload
-
end
-
-
11483
@proxy ||= CollectionProxy.create(klass, self)
-
11480
@proxy.reset_scope
-
end
-
-
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
-
3
def writer(records)
-
273
replace(records)
-
end
-
-
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
-
3
def ids_reader
-
162
if loaded?
-
48
target.pluck(reflection.association_primary_key)
-
114
elsif !target.empty?
-
12
load_target.pluck(reflection.association_primary_key)
-
else
-
102
@association_ids ||= scope.pluck(reflection.association_primary_key)
-
end
-
end
-
-
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
-
3
def ids_writer(ids)
-
89
primary_key = reflection.association_primary_key
-
89
pk_type = klass.type_for_attribute(primary_key)
-
89
ids = Array(ids).compact_blank
-
203
ids.map! { |i| pk_type.cast(i) }
-
-
89
records = klass.where(primary_key => ids).index_by do |r|
-
108
r.public_send(primary_key)
-
end.values_at(*ids).compact
-
-
89
if records.size != ids.size
-
12
found_ids = records.map { |record| record.public_send(primary_key) }
-
6
not_found_ids = ids - found_ids
-
6
klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
-
else
-
83
replace(records)
-
end
-
end
-
-
3
def reset
-
15349
super
-
15349
@target = []
-
15349
@association_ids = nil
-
end
-
-
3
def find(*args)
-
112
if options[:inverse_of] && loaded?
-
21
args_flatten = args.flatten
-
21
model = scope.klass
-
-
21
if args_flatten.blank?
-
3
error_message = "Couldn't find #{model.name} without an ID"
-
3
raise RecordNotFound.new(error_message, model.name, model.primary_key, args)
-
end
-
-
18
result = find_by_scan(*args)
-
-
18
result_size = Array(result).size
-
18
if !result || result_size != args_flatten.size
-
scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
-
else
-
18
result
-
end
-
else
-
91
scope.find(*args)
-
end
-
end
-
-
3
def build(attributes = nil, &block)
-
1974
if attributes.is_a?(Array)
-
54
attributes.collect { |attr| build(attr, &block) }
-
else
-
1956
add_to_target(build_record(attributes, &block))
-
end
-
end
-
-
# Add +records+ to this association. Since +<<+ flattens its argument list
-
# and inserts each record, +push+ and +concat+ behave identically.
-
3
def concat(*records)
-
967
records = records.flatten
-
967
if owner.new_record?
-
385
load_target
-
385
concat_records(records)
-
else
-
1164
transaction { concat_records(records) }
-
end
-
end
-
-
# Starts a transaction in the association class's database connection.
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :books
-
# end
-
#
-
# Author.first.books.transaction do
-
# # same effect as calling Book.transaction
-
# end
-
3
def transaction(*args)
-
2775
reflection.klass.transaction(*args) do
-
2775
yield
-
end
-
end
-
-
# Removes all records from the association without calling callbacks
-
# on the associated records. It honors the +:dependent+ option. However
-
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
-
# deletion strategy for the association is applied.
-
#
-
# You can force a particular deletion strategy by passing a parameter.
-
#
-
# Example:
-
#
-
# @author.books.delete_all(:nullify)
-
# @author.books.delete_all(:delete_all)
-
#
-
# See delete for more info.
-
3
def delete_all(dependent = nil)
-
735
if dependent && ![:nullify, :delete_all].include?(dependent)
-
3
raise ArgumentError, "Valid values are :nullify or :delete_all"
-
end
-
-
732
dependent = if dependent
-
444
dependent
-
288
elsif options[:dependent] == :destroy
-
18
:delete_all
-
else
-
270
options[:dependent]
-
end
-
-
732
delete_or_nullify_all_records(dependent).tap do
-
729
reset
-
729
loaded!
-
end
-
end
-
-
# Destroy all the records from this association.
-
#
-
# See destroy for more info.
-
3
def destroy_all
-
658
destroy(load_target).tap do
-
649
reset
-
649
loaded!
-
end
-
end
-
-
# Removes +records+ from this association calling +before_remove+ and
-
# +after_remove+ callbacks.
-
#
-
# This method is abstract in the sense that +delete_records+ has to be
-
# provided by descendants. Note this method does not imply the records
-
# are actually removed from the database, that depends precisely on
-
# +delete_records+. They are in any case removed from the collection.
-
3
def delete(*records)
-
470
delete_or_destroy(records, options[:dependent])
-
end
-
-
# Deletes the +records+ and removes them from this association calling
-
# +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
-
#
-
# Note that this method removes records from the database ignoring the
-
# +:dependent+ option.
-
3
def destroy(*records)
-
808
delete_or_destroy(records, :destroy)
-
end
-
-
# Returns the size of the collection by executing a SELECT COUNT(*)
-
# query if the collection hasn't been loaded, and calling
-
# <tt>collection.size</tt> if it has.
-
#
-
# If the collection has been already loaded +size+ and +length+ are
-
# equivalent. If not and you are going to need the records anyway
-
# +length+ will take one less query. Otherwise +size+ is more efficient.
-
#
-
# This method is abstract in the sense that it relies on
-
# +count_records+, which is a method descendants have to provide.
-
3
def size
-
1134
if !find_target? || loaded?
-
707
target.size
-
427
elsif @association_ids
-
18
@association_ids.size
-
409
elsif !association_scope.group_values.empty?
-
6
load_target.size
-
403
elsif !association_scope.distinct_value && !target.empty?
-
45
unsaved_records = target.select(&:new_record?)
-
45
unsaved_records.size + count_records
-
else
-
358
count_records
-
end
-
end
-
-
# Returns true if the collection is empty.
-
#
-
# If the collection has been loaded
-
# it is equivalent to <tt>collection.size.zero?</tt>. If the
-
# collection has not been loaded, it is equivalent to
-
# <tt>collection.exists?</tt>. If the collection has not already been
-
# loaded and you are going to fetch the records anyway it is better to
-
# check <tt>collection.length.zero?</tt>.
-
3
def empty?
-
240
if loaded? || @association_ids || reflection.has_cached_counter?
-
111
size.zero?
-
else
-
129
target.empty? && !scope.exists?
-
end
-
end
-
-
# Replace this collection with +other_array+. This will perform a diff
-
# and delete/add only records that have changed.
-
3
def replace(other_array)
-
809
other_array.each { |val| raise_on_type_mismatch!(val) }
-
362
original_target = load_target.dup
-
-
362
if owner.new_record?
-
201
replace_records(other_array, original_target)
-
else
-
161
replace_common_records_in_memory(other_array, original_target)
-
161
if other_array != original_target
-
280
transaction { replace_records(other_array, original_target) }
-
else
-
21
other_array
-
end
-
end
-
end
-
-
3
def include?(record)
-
288
if record.is_a?(reflection.klass)
-
282
if record.new_record?
-
21
include_in_memory?(record)
-
else
-
261
loaded? ? target.include?(record) : scope.exists?(record.id)
-
end
-
else
-
6
false
-
end
-
end
-
-
3
def load_target
-
6995
if find_target?
-
2821
@target = merge_target_lists(find_target, target)
-
end
-
-
6968
loaded!
-
6968
target
-
end
-
-
3
def add_to_target(record, skip_callbacks: false, replace: false, &block)
-
4702
if replace || association_scope.distinct_value
-
91
index = @target.index(record)
-
end
-
4702
replace_on_target(record, index, skip_callbacks, &block)
-
end
-
-
3
def target=(record)
-
3955
return super unless ActiveRecord::Base.has_many_inversing
-
-
18
case record
-
when Array
-
super
-
else
-
18
add_to_target(record, skip_callbacks: true, replace: true)
-
end
-
end
-
-
3
def scope
-
11691
scope = super
-
11691
scope.none! if null_scope?
-
11691
scope
-
end
-
-
3
def null_scope?
-
12645
owner.new_record? && !foreign_key_present?
-
end
-
-
3
def find_from_target?
-
864
loaded? ||
-
owner.strict_loading? ||
-
reflection.strict_loading? ||
-
owner.new_record? ||
-
90
target.any? { |record| record.new_record? || record.changed? }
-
end
-
-
3
private
-
# We have some records loaded from the database (persisted) and some that are
-
# in-memory (memory). The same record may be represented in the persisted array
-
# and in the memory array.
-
#
-
# So the task of this method is to merge them according to the following rules:
-
#
-
# * The final array must not have duplicates
-
# * The order of the persisted array is to be preserved
-
# * Any changes made to attributes on objects in the memory array are to be preserved
-
# * Otherwise, attributes should have the value found in the database
-
3
def merge_target_lists(persisted, memory)
-
2794
return persisted if memory.empty?
-
562
return memory if persisted.empty?
-
-
535
persisted.map! do |record|
-
1006
if mem_record = memory.delete(record)
-
-
820
((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
-
6156
mem_record[name] = record[name]
-
end
-
-
820
mem_record
-
else
-
186
record
-
end
-
end
-
-
535
persisted + memory
-
end
-
-
3
def _create_record(attributes, raise = false, &block)
-
1601
unless owner.persisted?
-
15
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
-
end
-
-
1586
if attributes.is_a?(Array)
-
18
attributes.collect { |attr| _create_record(attr, raise, &block) }
-
else
-
1580
record = build_record(attributes, &block)
-
1577
transaction do
-
1577
result = nil
-
1577
add_to_target(record) do
-
1577
result = insert_record(record, true, raise) {
-
1544
@_was_loaded = loaded?
-
}
-
end
-
1571
raise ActiveRecord::Rollback unless result
-
end
-
1571
record
-
end
-
end
-
-
# Do the relevant stuff to insert the given record into the association collection.
-
3
def insert_record(record, validate = true, raise = false, &block)
-
2712
if raise
-
952
record.save!(validate: validate, &block)
-
else
-
1760
record.save(validate: validate, &block)
-
end
-
end
-
-
3
def delete_or_destroy(records, method)
-
1278
return if records.empty?
-
2571
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
-
1278
records = records.flatten
-
1975
records.each { |record| raise_on_type_mismatch!(record) }
-
1269
existing_records = records.reject(&:new_record?)
-
-
1269
if existing_records.empty?
-
793
remove_records(existing_records, records, method)
-
else
-
952
transaction { remove_records(existing_records, records, method) }
-
end
-
end
-
-
3
def remove_records(existing_records, records, method)
-
catch(:abort) do
-
1957
records.each { |record| callback(:before_remove, record) }
-
1269
end || return
-
-
1266
delete_records(existing_records, method) if existing_records.any?
-
1224
@target -= records
-
1224
@association_ids = nil
-
-
1828
records.each { |record| callback(:after_remove, record) }
-
end
-
-
# Delete the given records from the association,
-
# using one of the methods +:destroy+, +:delete_all+
-
# or +:nullify+ (or +nil+, in which case a default is used).
-
3
def delete_records(records, method)
-
raise NotImplementedError
-
end
-
-
3
def replace_records(new_target, original_target)
-
341
delete(difference(target, new_target))
-
-
323
unless concat(difference(new_target, target))
-
3
@target = original_target
-
3
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
-
"new records could not be saved."
-
end
-
-
317
target
-
end
-
-
3
def replace_common_records_in_memory(new_target, original_target)
-
161
common_records = intersection(new_target, original_target)
-
161
common_records.each do |record|
-
57
skip_callbacks = true
-
57
replace_on_target(record, @target.index(record), skip_callbacks)
-
end
-
end
-
-
3
def concat_records(records, raise = false)
-
964
result = true
-
-
964
records.each do |record|
-
1070
raise_on_type_mismatch!(record)
-
1061
add_to_target(record) do
-
1055
unless owner.new_record?
-
586
result &&= insert_record(record, true, raise) {
-
270
@_was_loaded = loaded?
-
}
-
end
-
end
-
end
-
-
931
raise ActiveRecord::Rollback unless result
-
-
895
records
-
end
-
-
3
def replace_on_target(record, index, skip_callbacks)
-
catch(:abort) do
-
4579
callback(:before_add, record)
-
4759
end || return unless skip_callbacks
-
-
4753
set_inverse_instance(record)
-
-
4753
@_was_loaded = true
-
-
4753
yield(record) if block_given?
-
-
4726
if index
-
72
target[index] = record
-
4654
elsif @_was_loaded || !loaded?
-
4651
@association_ids = nil
-
4651
target << record
-
end
-
-
4726
callback(:after_add, record) unless skip_callbacks
-
-
4726
record
-
ensure
-
4759
@_was_loaded = nil
-
end
-
-
3
def callback(method, record)
-
10417
callbacks_for(method).each do |callback|
-
498
callback.call(method, owner, record)
-
end
-
end
-
-
3
def callbacks_for(callback_name)
-
10417
full_callback_name = "#{callback_name}_for_#{reflection.name}"
-
10417
owner.class.send(full_callback_name)
-
end
-
-
3
def include_in_memory?(record)
-
21
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
-
12
assoc = owner.association(reflection.through_reflection.name)
-
assoc.reader.any? { |source|
-
9
target_reflection = source.send(reflection.source_reflection.name)
-
9
target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
-
12
} || target.include?(record)
-
else
-
9
target.include?(record)
-
end
-
end
-
-
# If the :inverse_of option has been
-
# specified, then #find scans the entire collection.
-
3
def find_by_scan(*args)
-
18
expects_array = args.first.kind_of?(Array)
-
18
ids = args.flatten.compact.map(&:to_s).uniq
-
-
18
if ids.size == 1
-
18
id = ids.first
-
36
record = load_target.detect { |r| id == r.id.to_s }
-
18
expects_array ? [ record ] : record
-
else
-
load_target.select { |r| ids.include?(r.id.to_s) }
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# Collection proxies in Active Record are middlemen between an
-
# <tt>association</tt>, and its <tt>target</tt> result set.
-
#
-
# For example, given
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :posts
-
# end
-
#
-
# blog = Blog.first
-
#
-
# The collection proxy returned by <tt>blog.posts</tt> is built from a
-
# <tt>:has_many</tt> <tt>association</tt>, and delegates to a collection
-
# of posts as the <tt>target</tt>.
-
#
-
# This class delegates unknown methods to the <tt>association</tt>'s
-
# relation class via a delegate cache.
-
#
-
# The <tt>target</tt> result set is not loaded until needed. For example,
-
#
-
# blog.posts.count
-
#
-
# is computed directly through SQL and does not trigger by itself the
-
# instantiation of the actual post records.
-
3
class CollectionProxy < Relation
-
3
def initialize(klass, association, **) #:nodoc:
-
6730
@association = association
-
6730
super klass
-
-
6730
extensions = association.extensions
-
6730
extend(*extensions) if extensions.any?
-
end
-
-
3
def target
-
39
@association.target
-
end
-
-
3
def load_target
-
4486
@association.load_target
-
end
-
-
# Returns +true+ if the association has been loaded, otherwise +false+.
-
#
-
# person.pets.loaded? # => false
-
# person.pets
-
# person.pets.loaded? # => true
-
3
def loaded?
-
1930
@association.loaded?
-
end
-
3
alias :loaded :loaded?
-
-
##
-
# :method: select
-
#
-
# :call-seq:
-
# select(*fields, &block)
-
#
-
# Works in two ways.
-
#
-
# *First:* Specify a subset of fields to be selected from the result set.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet id: nil, name: "Fancy-Fancy">,
-
# # #<Pet id: nil, name: "Spook">,
-
# # #<Pet id: nil, name: "Choo-Choo">
-
# # ]
-
#
-
# person.pets.select(:id, :name)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy">,
-
# # #<Pet id: 2, name: "Spook">,
-
# # #<Pet id: 3, name: "Choo-Choo">
-
# # ]
-
#
-
# Be careful because this also means you're initializing a model
-
# object with only the fields that you've selected. If you attempt
-
# to access a field except +id+ that is not in the initialized record you'll
-
# receive:
-
#
-
# person.pets.select(:name).first.person_id
-
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
-
#
-
# *Second:* You can pass a block so it can be used just like Array#select.
-
# This builds an array of objects from the database for the scope,
-
# converting them into an array and iterating through them using
-
# Array#select.
-
#
-
# person.pets.select { |pet| /oo/.match?(pet.name) }
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
-
# Finds an object in the collection responding to the +id+. Uses the same
-
# rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
-
# error if the object cannot be found.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
# person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4
-
#
-
# person.pets.find(2) { |pet| pet.name.downcase! }
-
# # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
-
#
-
# person.pets.find(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
3
def find(*args)
-
91
return super if block_given?
-
91
@association.find(*args)
-
end
-
-
##
-
# :method: first
-
#
-
# :call-seq:
-
# first(limit = nil)
-
#
-
# Returns the first record, or the first +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.first(2)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.first # => nil
-
# another_person_without.pets.first(3) # => []
-
-
##
-
# :method: second
-
#
-
# :call-seq:
-
# second()
-
#
-
# Same as #first except returns only the second record.
-
-
##
-
# :method: third
-
#
-
# :call-seq:
-
# third()
-
#
-
# Same as #first except returns only the third record.
-
-
##
-
# :method: fourth
-
#
-
# :call-seq:
-
# fourth()
-
#
-
# Same as #first except returns only the fourth record.
-
-
##
-
# :method: fifth
-
#
-
# :call-seq:
-
# fifth()
-
#
-
# Same as #first except returns only the fifth record.
-
-
##
-
# :method: forty_two
-
#
-
# :call-seq:
-
# forty_two()
-
#
-
# Same as #first except returns only the forty second record.
-
# Also known as accessing "the reddit".
-
-
##
-
# :method: third_to_last
-
#
-
# :call-seq:
-
# third_to_last()
-
#
-
# Same as #first except returns only the third-to-last record.
-
-
##
-
# :method: second_to_last
-
#
-
# :call-seq:
-
# second_to_last()
-
#
-
# Same as #first except returns only the second-to-last record.
-
-
# Returns the last record, or the last +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
#
-
# person.pets.last(2)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.last # => nil
-
# another_person_without.pets.last(3) # => []
-
3
def last(limit = nil)
-
141
load_target if find_from_target?
-
141
super
-
end
-
-
# Gives a record (or N records if a parameter is supplied) from the collection
-
# using the same rules as <tt>ActiveRecord::Base.take</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.take(2)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.take # => nil
-
# another_person_without.pets.take(2) # => []
-
3
def take(limit = nil)
-
45
load_target if find_from_target?
-
45
super
-
end
-
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object, but have not yet been saved.
-
# You can pass an array of attributes hashes, this will return an array
-
# with the new objects.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.build
-
# # => #<Pet id: nil, name: nil, person_id: 1>
-
#
-
# person.pets.build(name: 'Fancy-Fancy')
-
# # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
-
# # => [
-
# # #<Pet id: nil, name: "Spook", person_id: 1>,
-
# # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
-
# # #<Pet id: nil, name: "Brain", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 5 # size of the collection
-
# person.pets.count # => 0 # count from database
-
3
def build(attributes = {}, &block)
-
821
@association.build(attributes, &block)
-
end
-
3
alias_method :new, :build
-
-
# Returns a new object of the collection type that has been instantiated with
-
# attributes, linked to this object and that has already been saved (if it
-
# passes the validations).
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.create(name: 'Fancy-Fancy')
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# person.pets.count # => 3
-
#
-
# person.pets.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
3
def create(attributes = {}, &block)
-
658
@association.create(attributes, &block)
-
end
-
-
# Like #create, except that if the record is invalid, raises an exception.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# class Pet
-
# validates :name, presence: true
-
# end
-
#
-
# person.pets.create!(name: nil)
-
# # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
-
3
def create!(attributes = {}, &block)
-
901
@association.create!(attributes, &block)
-
end
-
-
# Replaces this collection with +other_array+. This will perform a diff
-
# and delete/add only records that have changed.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
-
#
-
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities')]
-
#
-
# person.pets.replace(other_pets)
-
#
-
# person.pets
-
# # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
-
#
-
# If the supplied array has an incorrect association type, it raises
-
# an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
-
#
-
# person.pets.replace(["doo", "ggie", "gaga"])
-
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
-
3
def replace(other_array)
-
6
@association.replace(other_array)
-
end
-
-
# Deletes all the records from the collection according to the strategy
-
# specified by the +:dependent+ option. If no +:dependent+ option is given,
-
# then it will follow the default strategy.
-
#
-
# For <tt>has_many :through</tt> associations, the default deletion strategy is
-
# +:delete_all+.
-
#
-
# For +has_many+ associations, the default deletion strategy is +:nullify+.
-
# This sets the foreign keys to +NULL+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
-
# # #<Pet id: 2, name: "Spook", person_id: nil>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
-
# # ]
-
#
-
# Both +has_many+ and <tt>has_many :through</tt> dependencies default to the
-
# +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
-
# Records are not instantiated and callbacks will not be fired.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
-
#
-
# If it is set to <tt>:delete_all</tt>, all the objects are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
-
3
def delete_all(dependent = nil)
-
210
@association.delete_all(dependent).tap { reset_scope }
-
end
-
-
# Deletes the records of the collection directly from the database
-
# ignoring the +:dependent+ option. Records are instantiated and it
-
# invokes +before_remove+, +after_remove+ , +before_destroy+ and
-
# +after_destroy+ callbacks.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy_all
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1) # => Couldn't find Pet with id=1
-
3
def destroy_all
-
66
@association.destroy_all.tap { reset_scope }
-
end
-
-
# Deletes the +records+ supplied from the collection according to the strategy
-
# specified by the +:dependent+ option. If no +:dependent+ option is given,
-
# then it will follow the default strategy. Returns an array with the
-
# deleted records.
-
#
-
# For <tt>has_many :through</tt> associations, the default deletion strategy is
-
# +:delete_all+.
-
#
-
# For +has_many+ associations, the default deletion strategy is +:nullify+.
-
# This sets the foreign keys to +NULL+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
-
#
-
# If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
-
# their +destroy+ method. See +destroy+ for more information.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1), Pet.find(3))
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 1
-
# person.pets
-
# # => [#<Pet id: 2, name: "Spook", person_id: 1>]
-
#
-
# Pet.find(1, 3)
-
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
-
#
-
# If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
-
#
-
# You can pass +Integer+ or +String+ values, it finds the records
-
# responding to the +id+ and executes delete on them.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete("1")
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.delete(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
3
def delete(*records)
-
243
@association.delete(*records).tap { reset_scope }
-
end
-
-
# Destroys the +records+ supplied and removes them from the collection.
-
# This method will _always_ remove record from the database ignoring
-
# the +:dependent+ option. Returns an array with the removed records.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(2), Pet.find(3))
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
-
#
-
# You can pass +Integer+ or +String+ values, it finds the records
-
# responding to the +id+ and then deletes them from the database.
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy("4")
-
# # => #<Pet id: 4, name: "Benny", person_id: 1>
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(5, 6)
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
-
3
def destroy(*records)
-
96
@association.destroy(*records).tap { reset_scope }
-
end
-
-
##
-
# :method: distinct
-
#
-
# :call-seq:
-
# distinct(value = true)
-
#
-
# Specifies whether the records should be unique or not.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet name: "Fancy-Fancy">,
-
# # #<Pet name: "Fancy-Fancy">
-
# # ]
-
#
-
# person.pets.select(:name).distinct
-
# # => [#<Pet name: "Fancy-Fancy">]
-
#
-
# person.pets.select(:name).distinct.distinct(false)
-
# # => [
-
# # #<Pet name: "Fancy-Fancy">,
-
# # #<Pet name: "Fancy-Fancy">
-
# # ]
-
-
#--
-
3
def calculate(operation, column_name)
-
924
null_scope? ? scope.calculate(operation, column_name) : super
-
end
-
-
3
def pluck(*column_names)
-
30
null_scope? ? scope.pluck(*column_names) : super
-
end
-
-
##
-
# :method: count
-
#
-
# :call-seq:
-
# count(column_name = nil, &block)
-
#
-
# Count all records.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# # This will perform the count using SQL.
-
# person.pets.count # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Passing a block will select all of a person's pets in SQL and then
-
# perform the count using Ruby.
-
#
-
# person.pets.count { |pet| pet.name.include?('-') } # => 2
-
-
# Returns the size of the collection. If the collection hasn't been loaded,
-
# it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
-
#
-
# If the collection has been already loaded +size+ and +length+ are
-
# equivalent. If not and you are going to need the records anyway
-
# +length+ will take one less query. Otherwise +size+ is more efficient.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# person.pets # This will execute a SELECT * FROM query
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# # Because the collection is already loaded, this will behave like
-
# # collection.size and no SQL count query is executed.
-
3
def size
-
1023
@association.size
-
end
-
-
##
-
# :method: length
-
#
-
# :call-seq:
-
# length()
-
#
-
# Returns the size of the collection calling +size+ on the target.
-
# If the collection has been already loaded, +length+ and +size+ are
-
# equivalent. If not and you are going to need the records anyway this
-
# method will take one less query. Otherwise +size+ is more efficient.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.length # => 3
-
# # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# # Because the collection is loaded, you can
-
# # call the collection with no additional queries:
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
-
# Returns +true+ if the collection is empty. If the collection has been
-
# loaded it is equivalent
-
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
-
# it is equivalent to <tt>!collection.exists?</tt>. If the collection has
-
# not already been loaded and you are going to fetch the records anyway it
-
# is better to check <tt>collection.length.zero?</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 1
-
# person.pets.empty? # => false
-
#
-
# person.pets.delete_all
-
#
-
# person.pets.count # => 0
-
# person.pets.empty? # => true
-
3
def empty?
-
231
@association.empty?
-
end
-
-
##
-
# :method: any?
-
#
-
# :call-seq:
-
# any?()
-
#
-
# Returns +true+ if the collection is not empty.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 0
-
# person.pets.any? # => false
-
#
-
# person.pets << Pet.new(name: 'Snoop')
-
# person.pets.count # => 1
-
# person.pets.any? # => true
-
#
-
# You can also pass a +block+ to define criteria. The behavior
-
# is the same, it returns true if the collection based on the
-
# criteria is not empty.
-
#
-
# person.pets
-
# # => [#<Pet name: "Snoop", group: "dogs">]
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => false
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => true
-
-
##
-
# :method: many?
-
#
-
# :call-seq:
-
# many?()
-
#
-
# Returns true if the collection has more than one record.
-
# Equivalent to <tt>collection.size > 1</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 1
-
# person.pets.many? # => false
-
#
-
# person.pets << Pet.new(name: 'Snoopy')
-
# person.pets.count # => 2
-
# person.pets.many? # => true
-
#
-
# You can also pass a +block+ to define criteria. The
-
# behavior is the same, it returns true if the collection
-
# based on the criteria has more than one record.
-
#
-
# person.pets
-
# # => [
-
# # #<Pet name: "Gorby", group: "cats">,
-
# # #<Pet name: "Puff", group: "cats">,
-
# # #<Pet name: "Snoop", group: "dogs">
-
# # ]
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => false
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => true
-
-
# Returns +true+ if the given +record+ is present in the collection.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # => [#<Pet id: 20, name: "Snoop">]
-
#
-
# person.pets.include?(Pet.find(20)) # => true
-
# person.pets.include?(Pet.find(21)) # => false
-
3
def include?(record)
-
288
!!@association.include?(record)
-
end
-
-
3
def proxy_association # :nodoc:
-
1048
@association
-
end
-
-
# Returns a <tt>Relation</tt> object for the records in this association
-
3
def scope
-
16402
@scope ||= @association.scope
-
end
-
-
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
-
# contain the same number of elements and if each element is equal
-
# to the corresponding element in the +other+ array, otherwise returns
-
# +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# other = person.pets.to_ary
-
#
-
# person.pets == other
-
# # => true
-
#
-
# other = [Pet.new(id: 1), Pet.new(id: 2)]
-
#
-
# person.pets == other
-
# # => false
-
3
def ==(other)
-
522
load_target == other
-
end
-
-
##
-
# :method: to_ary
-
#
-
# :call-seq:
-
# to_ary()
-
#
-
# Returns a new array of objects from the collection. If the collection
-
# hasn't been loaded, it fetches the records from the database.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets = person.pets.to_ary
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets.replace([Pet.new(name: 'BooGoo')])
-
#
-
# other_pets
-
# # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
-
#
-
# person.pets
-
# # This is not affected by replace
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
-
3
def records # :nodoc:
-
3312
load_target
-
end
-
-
# Adds one or more +records+ to the collection by setting their foreign keys
-
# to the association's primary key. Since <tt><<</tt> flattens its argument list and
-
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
-
# so several appends may be chained together.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 0
-
# person.pets << Pet.new(name: 'Fancy-Fancy')
-
# person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
-
# person.pets.size # => 3
-
#
-
# person.id # => 1
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
3
def <<(*records)
-
653
proxy_association.concat(records) && self
-
end
-
3
alias_method :push, :<<
-
3
alias_method :append, :<<
-
3
alias_method :concat, :<<
-
-
3
def prepend(*args) # :nodoc:
-
3
raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
-
end
-
-
# Equivalent to +delete_all+. The difference is that returns +self+, instead
-
# of an array with the deleted objects, so methods can be chained. See
-
# +delete_all+ for more information.
-
# Note that because +delete_all+ removes records by directly
-
# running an SQL query into the database, the +updated_at+ column of
-
# the object is not changed.
-
3
def clear
-
54
delete_all
-
51
self
-
end
-
-
# Reloads the collection from the database. Returns +self+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets # uses the pets cache
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets.reload # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
3
def reload
-
341
proxy_association.reload(true)
-
341
reset_scope
-
end
-
-
# Unloads the association. Returns +self+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets # uses the pets cache
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets.reset # clears the pets cache
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
3
def reset
-
24
proxy_association.reset
-
24
proxy_association.reset_scope
-
24
reset_scope
-
end
-
-
3
def reset_scope # :nodoc:
-
12139
@offsets = @take = nil
-
12139
@scope = nil
-
12139
self
-
end
-
-
3
delegate_methods = [
-
QueryMethods,
-
SpawnMethods,
-
].flat_map { |klass|
-
6
klass.public_instance_methods(false)
-
} - self.public_instance_methods(false) - [:select] + [
-
:scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
-
]
-
-
3
delegate(*delegate_methods, to: :scope)
-
-
3
private
-
3
def find_nth_with_limit(index, limit)
-
660
load_target if find_from_target?
-
642
super
-
end
-
-
3
def find_nth_from_last(index)
-
18
load_target if find_from_target?
-
18
super
-
end
-
-
3
def null_scope?
-
954
@association.null_scope?
-
end
-
-
3
def find_from_target?
-
864
@association.find_from_target?
-
end
-
-
3
def exec_queries
-
39
load_target
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord::Associations
-
3
module ForeignAssociation # :nodoc:
-
3
def foreign_key_present?
-
1170
if reflection.klass.primary_key
-
1062
owner.attribute_present?(reflection.active_record_primary_key)
-
else
-
108
false
-
end
-
end
-
-
3
def nullified_owner_attributes
-
125
Hash.new.tap do |attrs|
-
125
attrs[reflection.foreign_key] = nil
-
125
attrs[reflection.type] = nil if reflection.type.present?
-
end
-
end
-
-
3
private
-
# Sets the owner attributes on the given record
-
3
def set_owner_attributes(record)
-
3044
return if options[:through]
-
-
2366
key = owner._read_attribute(reflection.join_foreign_key)
-
2366
record._write_attribute(reflection.join_primary_key, key)
-
-
2366
if reflection.type
-
126
record._write_attribute(reflection.type, owner.class.polymorphic_name)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Has Many Association
-
# This is the proxy that handles a has many association.
-
#
-
# If the association has a <tt>:through</tt> option further specialization
-
# is provided by its child HasManyThroughAssociation.
-
3
class HasManyAssociation < CollectionAssociation #:nodoc:
-
3
include ForeignAssociation
-
-
3
def handle_dependency
-
817
case options[:dependent]
-
when :restrict_with_exception
-
3
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
-
-
when :restrict_with_error
-
6
unless empty?
-
6
record = owner.class.human_attribute_name(reflection.name).downcase
-
6
owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
-
6
throw(:abort)
-
end
-
-
when :destroy
-
# No point in executing the counter update since we're going to destroy the parent anyway
-
778
load_target.each { |t| t.destroyed_by_association = reflection }
-
622
destroy_all
-
else
-
186
delete_all
-
end
-
end
-
-
3
def insert_record(record, validate = true, raise = false)
-
2712
set_owner_attributes(record)
-
2712
super
-
end
-
-
3
private
-
# Returns the number of records in this collection.
-
#
-
# If the association has a counter cache it gets that value. Otherwise
-
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
-
# there's one. Some configuration options like :group make it impossible
-
# to do an SQL count, in those cases the array count will be used.
-
#
-
# That does not depend on whether the collection has already been loaded
-
# or not. The +size+ method is the one that takes the loaded flag into
-
# account and delegates to +count_records+ if needed.
-
#
-
# If the collection is empty the target is set to an empty array and
-
# the loaded flag is set to true as well.
-
3
def count_records
-
403
count = if reflection.has_cached_counter?
-
120
owner.read_attribute(reflection.counter_cache_column).to_i
-
else
-
283
scope.count(:all)
-
end
-
-
# If there's nothing in the database and @target has no new records
-
# we are certain the current target is an empty array. This is a
-
# documented side-effect of the method that may avoid an extra SELECT.
-
403
loaded! if count == 0
-
-
403
[association_scope.limit_value, count].compact.min
-
end
-
-
3
def update_counter(difference, reflection = reflection())
-
1106
if reflection.has_cached_counter?
-
90
owner.increment!(reflection.counter_cache_column, difference)
-
end
-
end
-
-
3
def update_counter_in_memory(difference, reflection = reflection())
-
2466
if reflection.counter_must_be_updated_by_has_many?
-
136
counter = reflection.counter_cache_column
-
136
owner.increment(counter, difference)
-
136
owner.send(:"clear_#{counter}_change")
-
end
-
end
-
-
3
def delete_count(method, scope)
-
704
if method == :delete_all
-
588
scope.delete_all
-
else
-
116
scope.update_all(nullified_owner_attributes)
-
end
-
end
-
-
3
def delete_or_nullify_all_records(method)
-
636
count = delete_count(method, scope)
-
636
update_counter(-count)
-
636
count
-
end
-
-
# Deletes the records according to the <tt>:dependent</tt> option.
-
3
def delete_records(records, method)
-
266
if method == :destroy
-
198
records.each(&:destroy!)
-
180
update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
-
else
-
68
scope = self.scope.where(reflection.klass.primary_key => records)
-
68
update_counter(-delete_count(method, scope))
-
end
-
end
-
-
3
def concat_records(records, *)
-
964
update_counter_if_success(super, records.length)
-
end
-
-
3
def _create_record(attributes, *)
-
1601
if attributes.is_a?(Array)
-
6
super
-
else
-
1595
update_counter_if_success(super, 1)
-
end
-
end
-
-
3
def update_counter_if_success(saved_successfully, difference)
-
2466
if saved_successfully
-
2466
update_counter_in_memory(difference)
-
end
-
2466
saved_successfully
-
end
-
-
3
def difference(a, b)
-
391
a - b
-
end
-
-
3
def intersection(a, b)
-
80
a & b
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Has Many Through Association
-
3
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
-
3
include ThroughAssociation
-
-
3
def initialize(owner, reflection)
-
4292
super
-
4280
@through_records = {}.compare_by_identity
-
end
-
-
3
def concat(*records)
-
421
unless owner.new_record?
-
298
records.flatten.each do |record|
-
319
raise_on_type_mismatch!(record)
-
end
-
end
-
-
412
super
-
end
-
-
3
def insert_record(record, validate = true, raise = false)
-
1045
ensure_not_nested
-
-
1045
if record.new_record? || record.has_changes_to_save?
-
678
return unless super
-
end
-
-
1030
save_through_record(record)
-
-
1018
record
-
end
-
-
3
private
-
3
def concat_records(records)
-
412
ensure_not_nested
-
-
409
records = super(records, true)
-
-
394
if owner.new_record? && records
-
123
records.flatten.each do |record|
-
144
build_through_record(record)
-
end
-
end
-
-
394
records
-
end
-
-
# The through record (built with build_record) is temporarily cached
-
# so that it may be reused if insert_record is subsequently called.
-
#
-
# However, after insert_record has been called, the cache is cleared in
-
# order to allow multiple instances of the same record in an association.
-
3
def build_through_record(record)
-
1234
@through_records[record] ||= begin
-
1084
ensure_mutable
-
-
1078
attributes = through_scope_attributes
-
1078
attributes[source_reflection.name] = record
-
1078
attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
-
-
1078
through_association.build(attributes)
-
end
-
end
-
-
3
def through_scope_attributes
-
scope.where_values_hash(through_association.reflection.name.to_s).
-
1354
except!(through_association.reflection.foreign_key,
-
through_association.reflection.klass.inheritance_column)
-
end
-
-
3
def save_through_record(record)
-
1030
association = build_through_record(record)
-
1024
if association.changed?
-
874
association.save!
-
end
-
ensure
-
1030
@through_records.delete(record)
-
end
-
-
3
def build_record(attributes)
-
747
ensure_not_nested
-
-
741
record = super
-
-
735
inverse = source_reflection.inverse_of
-
735
if inverse
-
63
if inverse.collection?
-
54
record.send(inverse.name) << build_through_record(record)
-
9
elsif inverse.has_one?
-
6
record.send("#{inverse.name}=", build_through_record(record))
-
end
-
end
-
-
735
record
-
end
-
-
3
def remove_records(existing_records, records, method)
-
336
super
-
312
delete_through_records(records)
-
end
-
-
3
def target_reflection_has_associated_record?
-
1149
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
-
end
-
-
3
def update_through_counter?(method)
-
273
case method
-
when :destroy
-
96
!through_reflection.inverse_updates_counter_cache?
-
when :nullify
-
45
false
-
else
-
132
true
-
end
-
end
-
-
3
def delete_or_nullify_all_records(method)
-
96
delete_records(load_target, method)
-
end
-
-
3
def delete_records(records, method)
-
303
ensure_not_nested
-
-
291
scope = through_association.scope
-
291
scope.where! construct_join_attributes(*records)
-
276
scope = scope.where(through_scope_attributes)
-
-
276
case method
-
when :destroy
-
96
if scope.klass.primary_key
-
36
count = scope.destroy_all.count(&:destroyed?)
-
else
-
60
scope.each(&:_run_destroy_callbacks)
-
60
count = scope.delete_all
-
end
-
when :nullify
-
45
count = scope.update_all(source_reflection.foreign_key => nil)
-
else
-
135
count = scope.delete_all
-
end
-
-
276
delete_through_records(records)
-
-
276
if source_reflection.options[:counter_cache] && method != :destroy
-
12
counter = source_reflection.counter_cache_column
-
12
klass.decrement_counter counter, records.map(&:id)
-
end
-
-
276
if through_reflection.collection? && update_through_counter?(method)
-
225
update_counter(-count, through_reflection)
-
else
-
51
update_counter(-count)
-
end
-
-
276
count
-
end
-
-
3
def difference(a, b)
-
273
distribution = distribution(b)
-
-
519
a.reject { |record| mark_occurrence(distribution, record) }
-
end
-
-
3
def intersection(a, b)
-
81
distribution = distribution(b)
-
-
186
a.select { |record| mark_occurrence(distribution, record) }
-
end
-
-
3
def mark_occurrence(distribution, record)
-
351
distribution[record] > 0 && distribution[record] -= 1
-
end
-
-
3
def distribution(array)
-
354
array.each_with_object(Hash.new(0)) do |record, distribution|
-
291
distribution[record] += 1
-
end
-
end
-
-
3
def through_records_for(record)
-
523
attributes = construct_join_attributes(record)
-
523
candidates = Array.wrap(through_association.target)
-
523
candidates.find_all do |c|
-
264
attributes.all? do |key, value|
-
270
c.public_send(key) == value
-
end
-
end
-
end
-
-
3
def delete_through_records(records)
-
588
records.each do |record|
-
523
through_records = through_records_for(record)
-
-
523
if through_reflection.collection?
-
646
through_records.each { |r| through_association.target.delete(r) }
-
else
-
6
if through_records.include?(through_association.target)
-
through_association.target = nil
-
end
-
end
-
-
523
@through_records.delete(record)
-
end
-
end
-
-
3
def find_target
-
1149
return [] unless target_reflection_has_associated_record?
-
1146
super
-
end
-
-
# NOTE - not sure that we can actually cope with inverses here
-
3
def invertible_for?(record)
-
4595
false
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Has One Association
-
3
class HasOneAssociation < SingularAssociation #:nodoc:
-
3
include ForeignAssociation
-
-
3
def handle_dependency
-
132
case options[:dependent]
-
when :restrict_with_exception
-
6
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
-
-
when :restrict_with_error
-
12
if load_target
-
6
record = owner.class.human_attribute_name(reflection.name).downcase
-
6
owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record)
-
6
throw(:abort)
-
end
-
-
else
-
114
delete
-
end
-
end
-
-
3
def delete(method = options[:dependent])
-
114
if load_target
-
63
case method
-
when :delete
-
12
target.delete
-
when :destroy
-
39
target.destroyed_by_association = reflection
-
39
target.destroy
-
39
throw(:abort) unless target.destroyed?
-
when :nullify
-
12
target.update_columns(nullified_owner_attributes) if target.persisted?
-
end
-
end
-
end
-
-
3
private
-
3
def replace(record, save = true)
-
681
raise_on_type_mismatch!(record) if record
-
-
675
return target unless load_target || record
-
-
672
assigning_another_record = target != record
-
672
if assigning_another_record || record.has_changes_to_save?
-
338
save &&= owner.persisted?
-
-
338
transaction_if(save) do
-
338
remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
-
-
335
if record
-
326
set_owner_attributes(record)
-
326
set_inverse_instance(record)
-
-
326
if save && !record.save
-
6
nullify_owner_attributes(record)
-
6
set_owner_attributes(target) if target
-
6
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
-
end
-
end
-
end
-
end
-
-
663
self.target = record
-
end
-
-
# The reason that the save param for replace is false, if for create (not just build),
-
# is because the setting of the foreign keys is actually handled by the scoping when
-
# the record is instantiated, and so they are set straight away and do not need to be
-
# updated within replace.
-
3
def set_new_record(record)
-
498
replace(record, false)
-
end
-
-
3
def remove_target!(method)
-
113
case method
-
when :delete
-
3
target.delete
-
when :destroy
-
30
target.destroyed_by_association = reflection
-
30
if target.persisted?
-
27
target.destroy
-
end
-
else
-
80
nullify_owner_attributes(target)
-
80
remove_inverse_instance(target)
-
-
80
if target.persisted? && owner.persisted? && !target.save
-
3
set_owner_attributes(target)
-
3
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
-
"The record failed to save after its foreign key was set to nil."
-
end
-
end
-
end
-
-
3
def nullify_owner_attributes(record)
-
86
record[reflection.foreign_key] = nil
-
end
-
-
3
def transaction_if(value)
-
338
if value
-
156
reflection.klass.transaction { yield }
-
else
-
260
yield
-
end
-
end
-
-
3
def _create_record(attributes, raise_error = false, &block)
-
378
unless owner.persisted?
-
3
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
-
end
-
-
375
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Has One Through Association
-
3
class HasOneThroughAssociation < HasOneAssociation #:nodoc:
-
3
include ThroughAssociation
-
-
3
private
-
3
def replace(record, save = true)
-
66
create_through_record(record, save)
-
63
self.target = record
-
end
-
-
3
def create_through_record(record, save)
-
66
ensure_not_nested
-
-
63
through_proxy = through_association
-
63
through_record = through_proxy.load_target
-
-
63
if through_record && !record
-
6
through_record.destroy
-
57
elsif record
-
57
attributes = construct_join_attributes(record)
-
-
57
if through_record && through_record.destroyed?
-
3
through_record = through_proxy.tap(&:reload).target
-
end
-
-
57
if through_record
-
24
if through_record.new_record?
-
6
through_record.assign_attributes(attributes)
-
else
-
18
through_record.update(attributes)
-
end
-
33
elsif owner.new_record? || !save
-
24
through_proxy.build(attributes)
-
else
-
9
through_proxy.create(attributes)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class JoinDependency # :nodoc:
-
3
autoload :JoinBase, "active_record/associations/join_dependency/join_base"
-
3
autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
-
-
3
class Aliases # :nodoc:
-
3
def initialize(tables)
-
535
@tables = tables
-
535
@alias_cache = tables.each_with_object({}) { |table, h|
-
1235
h[table.node] = table.columns.each_with_object({}) { |column, i|
-
10559
i[column.name] = column.alias
-
}
-
}
-
535
@columns_cache = tables.each_with_object({}) { |table, h|
-
1235
h[table.node] = table.columns
-
}
-
end
-
-
3
def columns
-
535
@tables.flat_map(&:column_aliases)
-
end
-
-
3
def column_aliases(node)
-
2053
@columns_cache[node]
-
end
-
-
3
def column_alias(node, column)
-
3017
@alias_cache[node][column]
-
end
-
-
3
Table = Struct.new(:node, :columns) do # :nodoc:
-
3
def column_aliases
-
1235
t = node.table
-
11794
columns.map { |column| t[column.name].as(column.alias) }
-
end
-
end
-
3
Column = Struct.new(:name, :alias)
-
end
-
-
3
def self.make_tree(associations)
-
4018
hash = {}
-
4018
walk_tree associations, hash
-
4018
hash
-
end
-
-
3
def self.walk_tree(associations, hash)
-
6527
case associations
-
when Symbol, String
-
2191
hash[associations.to_sym] ||= {}
-
when Array
-
4057
associations.each do |assoc|
-
2212
walk_tree assoc, hash
-
end
-
when Hash
-
279
associations.each do |k, v|
-
297
cache = hash[k] ||= {}
-
297
walk_tree v, cache
-
end
-
else
-
raise ConfigurationError, associations.inspect
-
end
-
end
-
-
3
def initialize(base, table, associations, join_type)
-
4018
tree = self.class.make_tree associations
-
4018
@join_root = JoinBase.new(base, table, build(tree, base))
-
3991
@join_type = join_type
-
end
-
-
3
def base_klass
-
1211
join_root.base_klass
-
end
-
-
3
def reflections
-
958
join_root.drop(1).map!(&:reflection)
-
end
-
-
3
def join_constraints(joins_to_add, alias_tracker)
-
2114
@alias_tracker = alias_tracker
-
2114
@joined_tables = {}
-
-
2114
joins = make_join_constraints(join_root, join_type)
-
-
2114
joins.concat joins_to_add.flat_map { |oj|
-
1301
if join_root.match? oj.join_root
-
1208
walk(join_root, oj.join_root, oj.join_type)
-
else
-
93
make_join_constraints(oj.join_root, oj.join_type)
-
end
-
}
-
end
-
-
3
def instantiate(result_set, strict_loading_value, &block)
-
526
primary_key = aliases.column_alias(join_root, join_root.primary_key)
-
-
526
seen = Hash.new { |i, parent|
-
1086
i[parent] = Hash.new { |j, child_class|
-
1170
j[child_class] = {}
-
}
-
}.compare_by_identity
-
-
1667
model_cache = Hash.new { |h, klass| h[klass] = {} }
-
526
parents = model_cache[join_root]
-
-
526
column_aliases = aliases.column_aliases(join_root)
-
526
column_names = []
-
-
526
result_set.columns.each do |name|
-
10448
column_names << name unless /\At\d+_r\d+\z/.match?(name)
-
end
-
-
526
if column_names.empty?
-
496
column_types = {}
-
else
-
30
column_types = result_set.column_types
-
30
unless column_types.empty?
-
attribute_types = join_root.attribute_types
-
column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
-
end
-
162
column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
-
end
-
-
526
message_bus = ActiveSupport::Notifications.instrumenter
-
-
526
payload = {
-
record_count: result_set.length,
-
class_name: join_root.base_klass.name
-
}
-
-
526
message_bus.instrument("instantiation.active_record", payload) do
-
526
result_set.each { |row_hash|
-
2316
parent_key = primary_key ? row_hash[primary_key] : row_hash
-
2316
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
-
2316
construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
-
}
-
end
-
-
526
parents.values
-
end
-
-
3
def apply_column_aliases(relation)
-
535
@join_root_alias = relation.select_values.empty?
-
1070
relation._select!(-> { aliases.columns })
-
end
-
-
3
def each(&block)
-
628
join_root.each(&block)
-
end
-
-
3
protected
-
3
attr_reader :join_root, :join_type
-
-
3
private
-
3
attr_reader :alias_tracker, :join_root_alias
-
-
3
def aliases
-
5605
@aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
-
1235
column_names = if join_part == join_root && !join_root_alias
-
30
primary_key = join_root.primary_key
-
30
primary_key ? [primary_key] : []
-
else
-
1205
join_part.column_names
-
end
-
-
1235
columns = column_names.each_with_index.map { |column_name, j|
-
10559
Aliases::Column.new column_name, "t#{i}_r#{j}"
-
}
-
1235
Aliases::Table.new(join_part, columns)
-
}
-
end
-
-
3
def make_join_constraints(join_root, join_type)
-
2207
join_root.children.flat_map do |child|
-
1086
make_constraints(join_root, child, join_type)
-
end
-
end
-
-
3
def make_constraints(parent, child, join_type)
-
2528
foreign_table = parent.table
-
2528
foreign_klass = parent.base_klass
-
2528
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
-
3151
table, terminated = @joined_tables[reflection]
-
3151
root = reflection == child.reflection
-
-
3151
if table && (!root || !terminated)
-
12
@joined_tables[reflection] = [table, root] if root
-
12
next table, true
-
end
-
-
3139
table = alias_tracker.aliased_table_for(reflection.klass.arel_table) do
-
447
name = reflection.alias_candidate(parent.table_name)
-
447
root ? name : "#{name}_join"
-
end
-
-
3139
@joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
-
3139
table
-
288
end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
-
end
-
-
3
def walk(left, right, join_type)
-
1253
intersection, missing = right.children.map { |node1|
-
1271
[left.children.find { |node2| node1.match? node2 }, node1]
-
}.partition(&:first)
-
-
1298
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
-
2407
joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
-
end
-
-
3
def find_reflection(klass, name)
-
2464
klass._reflect_on_association(name) ||
-
raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?")
-
end
-
-
3
def build(associations, base_klass)
-
6455
associations.map do |name, right|
-
2464
reflection = find_reflection base_klass, name
-
2455
reflection.check_validity!
-
2455
reflection.check_eager_loadable!
-
-
2449
if reflection.polymorphic?
-
12
raise EagerLoadPolymorphicError.new(reflection)
-
end
-
-
2437
JoinAssociation.new(reflection, build(right, reflection.klass))
-
end
-
end
-
-
3
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
-
4817
return if ar_parent.nil?
-
-
4817
parent.children.each do |node|
-
2887
if node.reflection.collection?
-
1803
other = ar_parent.association(node.reflection.name)
-
1803
other.loaded!
-
1084
elsif ar_parent.association_cached?(node.reflection.name)
-
396
model = ar_parent.association(node.reflection.name).target
-
396
construct(model, node, row, seen, model_cache, strict_loading_value)
-
396
next
-
end
-
-
2491
key = aliases.column_alias(node, node.primary_key)
-
2491
id = row[key]
-
2491
if id.nil?
-
386
nil_association = ar_parent.association(node.reflection.name)
-
386
nil_association.loaded!
-
386
next
-
end
-
-
2105
model = seen[ar_parent][node][id]
-
-
2105
if model
-
339
construct(model, node, row, seen, model_cache, strict_loading_value)
-
else
-
1766
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
-
-
1766
seen[ar_parent][node][id] = model
-
1766
construct(model, node, row, seen, model_cache, strict_loading_value)
-
end
-
end
-
end
-
-
3
def construct_model(record, node, row, model_cache, id, strict_loading_value)
-
1766
other = record.association(node.reflection.name)
-
-
1766
model = model_cache[node][id] ||=
-
node.instantiate(row, aliases.column_aliases(node)) do |m|
-
1527
m.strict_loading! if strict_loading_value
-
1527
other.set_inverse_instance(m)
-
end
-
-
1766
if node.reflection.collection?
-
1204
other.target.push(model)
-
else
-
562
other.target = model
-
end
-
-
1766
model.readonly! if node.readonly?
-
1766
model.strict_loading! if node.strict_loading?
-
1766
model
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/associations/join_dependency/join_part"
-
3
require "active_support/core_ext/array/extract"
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class JoinDependency # :nodoc:
-
3
class JoinAssociation < JoinPart # :nodoc:
-
3
attr_reader :reflection, :tables
-
3
attr_accessor :table
-
-
3
def initialize(reflection, children)
-
2437
super(reflection.klass, children)
-
-
2437
@reflection = reflection
-
end
-
-
3
def match?(other)
-
72
return true if self == other
-
72
super && reflection == other.reflection
-
end
-
-
3
def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
-
2528
joins = []
-
2528
chain = []
-
-
2528
reflection.chain.each do |reflection|
-
3151
table, terminated = yield reflection
-
3151
@table ||= table
-
-
3151
if terminated
-
12
foreign_table, foreign_klass = table, reflection.klass
-
12
break
-
end
-
-
3139
chain << [reflection, table]
-
end
-
-
# The chain starts with the target table, but we want to end with it here (makes
-
# more sense in this context), so we reverse
-
2528
chain.reverse_each do |reflection, table|
-
3139
klass = reflection.klass
-
-
3139
join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
-
-
3139
unless join_scope.references_values.empty?
-
141
join_dependency = join_scope.construct_join_dependency(
-
join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin
-
)
-
141
join_scope.joins!(join_dependency)
-
end
-
-
3139
arel = join_scope.arel(alias_tracker.aliases)
-
3139
nodes = arel.constraints.first
-
-
3139
if nodes.is_a?(Arel::Nodes::And)
-
549
others = nodes.children.extract! do |node|
-
2400
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
-
end
-
end
-
-
3139
joins << join_type.new(table, Arel::Nodes::On.new(nodes))
-
-
3139
if others && !others.empty?
-
63
joins.concat arel.join_sources
-
63
append_constraints(joins.last, others)
-
end
-
-
# The current table in this iteration becomes the foreign table in the next
-
3139
foreign_table, foreign_klass = table, klass
-
end
-
-
2528
joins
-
end
-
-
3
def readonly?
-
1766
return @readonly if defined?(@readonly)
-
-
615
@readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
-
end
-
-
3
def strict_loading?
-
1766
return @strict_loading if defined?(@strict_loading)
-
-
615
@strict_loading = reflection.scope && reflection.scope_for(base_klass.unscoped).strict_loading_value
-
end
-
-
3
private
-
3
def append_constraints(join, constraints)
-
63
if join.is_a?(Arel::Nodes::StringJoin)
-
6
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
-
6
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
-
else
-
57
right = join.right
-
57
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/associations/join_dependency/join_part"
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class JoinDependency # :nodoc:
-
3
class JoinBase < JoinPart # :nodoc:
-
3
attr_reader :table
-
-
3
def initialize(base_klass, table, children)
-
3991
super(base_klass, children)
-
3991
@table = table
-
end
-
-
3
def match?(other)
-
1301
return true if self == other
-
1301
super && base_klass == other.base_klass
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class JoinDependency # :nodoc:
-
# A JoinPart represents a part of a JoinDependency. It is inherited
-
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
-
# everything else is being joined onto. A JoinAssociation represents an association which
-
# is joining to the base. A JoinAssociation may result in more than one actual join
-
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
-
# two; one for the join table and one for the target table).
-
3
class JoinPart # :nodoc:
-
3
include Enumerable
-
-
# The Active Record class which this join part is associated 'about'; for a JoinBase
-
# this is the actual base model, for a JoinAssociation this is the target model of the
-
# association.
-
3
attr_reader :base_klass, :children
-
-
3
delegate :table_name, :column_names, :primary_key, :attribute_types, to: :base_klass
-
-
3
def initialize(base_klass, children)
-
6428
@base_klass = base_klass
-
6428
@children = children
-
end
-
-
3
def match?(other)
-
1373
self.class == other.class
-
end
-
-
3
def each(&block)
-
3998
yield self
-
5767
children.each { |child| child.each(&block) }
-
end
-
-
3
def each_children(&block)
-
children.each do |child|
-
yield self, child
-
child.each_children(&block)
-
end
-
end
-
-
# An Arel::Table for the active_record
-
3
def table
-
raise NotImplementedError
-
end
-
-
3
def extract_record(row, column_names_with_alias)
-
# This code is performance critical as it is called per row.
-
# see: https://github.com/rails/rails/pull/12185
-
2812
hash = {}
-
-
2812
index = 0
-
2812
length = column_names_with_alias.length
-
-
2812
while index < length
-
29960
column = column_names_with_alias[index]
-
29960
hash[column.name] = row[column.alias]
-
29960
index += 1
-
end
-
-
2812
hash
-
end
-
-
3
def instantiate(row, aliases, column_types = {}, &block)
-
2812
base_klass.instantiate(extract_record(row, aliases), column_types, &block)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
-
3
module ActiveRecord
-
3
module Associations
-
# Implements the details of eager loading of Active Record associations.
-
#
-
# Suppose that you have the following two Active Record models:
-
#
-
# class Author < ActiveRecord::Base
-
# # columns: name, age
-
# has_many :books
-
# end
-
#
-
# class Book < ActiveRecord::Base
-
# # columns: title, sales, author_id
-
# end
-
#
-
# When you load an author with all associated books Active Record will make
-
# multiple queries like this:
-
#
-
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
-
#
-
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
-
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
-
#
-
# Active Record saves the ids of the records from the first query to use in
-
# the second. Depending on the number of associations involved there can be
-
# arbitrarily many SQL queries made.
-
#
-
# However, if there is a WHERE clause that spans across tables Active
-
# Record will fall back to a slightly more resource-intensive single query:
-
#
-
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
-
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
-
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
-
# FROM `authors`
-
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
-
# WHERE `books`.`title` = 'Illiad'
-
#
-
# This could result in many rows that contain redundant data and it performs poorly at scale
-
# and is therefore only used when necessary.
-
#
-
3
class Preloader #:nodoc:
-
3
extend ActiveSupport::Autoload
-
-
3
eager_autoload do
-
3
autoload :Association, "active_record/associations/preloader/association"
-
3
autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
-
end
-
-
# Eager loads the named associations for the given Active Record record(s).
-
#
-
# In this description, 'association name' shall refer to the name passed
-
# to an association creation method. For example, a model that specifies
-
# <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
-
# names +:author+ and +:buyers+.
-
#
-
# == Parameters
-
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
-
# i.e. +records+ itself may also contain arrays of records. In any case,
-
# +preload_associations+ will preload all associations records by
-
# flattening +records+.
-
#
-
# +associations+ specifies one or more associations that you want to
-
# preload. It may be:
-
# - a Symbol or a String which specifies a single association name. For
-
# example, specifying +:books+ allows this method to preload all books
-
# for an Author.
-
# - an Array which specifies multiple association names. This array
-
# is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
-
# allows this method to preload an author's avatar as well as all of his
-
# books.
-
# - a Hash which specifies multiple association names, as well as
-
# association names for the to-be-preloaded association objects. For
-
# example, specifying <tt>{ author: :avatar }</tt> will preload a
-
# book's author, as well as that author's avatar.
-
#
-
# +:associations+ has the same format as the +:include+ option for
-
# <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
-
#
-
# :books
-
# [ :books, :author ]
-
# { author: :avatar }
-
# [ :books, { author: :avatar } ]
-
3
def preload(records, associations, preload_scope = nil)
-
2554
records = Array.wrap(records).compact
-
-
2554
if records.empty?
-
141
[]
-
else
-
2413
Array.wrap(associations).flat_map { |association|
-
2413
preloaders_on association, records, preload_scope
-
}
-
end
-
end
-
-
3
def initialize(associate_by_default: true)
-
1330
@associate_by_default = associate_by_default
-
end
-
-
3
private
-
# Loads all the given data into +records+ for the +association+.
-
3
def preloaders_on(association, records, scope, polymorphic_parent = false)
-
2602
case association
-
when Hash
-
183
preloaders_for_hash(association, records, scope, polymorphic_parent)
-
when Symbol, String
-
2416
preloaders_for_one(association, records, scope, polymorphic_parent)
-
else
-
3
raise ArgumentError, "#{association.inspect} was not recognized for preload"
-
end
-
end
-
-
3
def preloaders_for_hash(association, records, scope, polymorphic_parent)
-
183
association.flat_map { |parent, child|
-
186
grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
-
177
loaders = preloaders_for_reflection(reflection, reflection_records, scope)
-
177
recs = loaders.flat_map(&:preloaded_records).uniq
-
177
child_polymorphic_parent = reflection && reflection.options[:polymorphic]
-
177
loaders.concat Array.wrap(child).flat_map { |assoc|
-
189
preloaders_on assoc, recs, scope, child_polymorphic_parent
-
}
-
171
loaders
-
end
-
}
-
end
-
-
# Loads all the given data into +records+ for a singular +association+.
-
#
-
# Functions by instantiating a preloader class such as Preloader::Association and
-
# call the +run+ method for each passed in class in the +records+ argument.
-
#
-
# Not all records have the same class, so group then preload group on the reflection
-
# itself so that if various subclass share the same association then we do not split
-
# them unnecessarily
-
#
-
# Additionally, polymorphic belongs_to associations can have multiple associated
-
# classes, depending on the polymorphic_type field. So we group by the classes as
-
# well.
-
3
def preloaders_for_one(association, records, scope, polymorphic_parent)
-
grouped_records(association, records, polymorphic_parent)
-
2416
.flat_map do |reflection, reflection_records|
-
2398
preloaders_for_reflection reflection, reflection_records, scope
-
end
-
end
-
-
3
def preloaders_for_reflection(reflection, records, scope)
-
207321
records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
-
2605
preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
-
end
-
end
-
-
3
def grouped_records(association, records, polymorphic_parent)
-
2602
h = {}
-
2602
records.each do |record|
-
204833
reflection = record.class._reflect_on_association(association)
-
204833
next if polymorphic_parent && !reflection || !record.association(association).klass
-
204746
(h[reflection] ||= []) << record
-
end
-
2587
h
-
end
-
-
3
class AlreadyLoaded # :nodoc:
-
3
def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
-
141
@owners = owners
-
141
@reflection = reflection
-
end
-
-
3
def run
-
141
self
-
end
-
-
3
def preloaded_records
-
42
@preloaded_records ||= records_by_owner.flat_map(&:last)
-
end
-
-
3
def records_by_owner
-
129
@records_by_owner ||= owners.index_with do |owner|
-
180
Array(owner.association(reflection.name).target)
-
end
-
end
-
-
3
private
-
3
attr_reader :owners, :reflection
-
end
-
-
# Returns a class containing the logic needed to load preload the data
-
# and attach it to a relation. The class returned implements a `run` method
-
# that accepts a preloader.
-
3
def preloader_for(reflection, owners)
-
5357
if owners.all? { |o| o.association(reflection.name).loaded? }
-
141
return AlreadyLoaded
-
end
-
2464
reflection.check_preloadable!
-
-
2449
if reflection.options[:through]
-
522
ThroughAssociation
-
else
-
1927
Association
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class Preloader
-
3
class Association #:nodoc:
-
3
def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
-
2449
@klass = klass
-
2449
@owners = owners.uniq(&:__id__)
-
2449
@reflection = reflection
-
2449
@preload_scope = preload_scope
-
2449
@associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
-
2449
@model = owners.first && owners.first.class
-
end
-
-
3
def run
-
2449
records = records_by_owner
-
-
owners.each do |owner|
-
203933
associate_records_to_owner(owner, records[owner] || [])
-
2449
end if @associate
-
-
2449
self
-
end
-
-
3
def records_by_owner
-
2800
load_records unless defined?(@records_by_owner)
-
-
2800
@records_by_owner
-
end
-
-
3
def preloaded_records
-
723
load_records unless defined?(@preloaded_records)
-
-
723
@preloaded_records
-
end
-
-
3
private
-
3
attr_reader :owners, :reflection, :preload_scope, :model, :klass
-
-
3
def load_records
-
# owners can be duplicated when a relation has a collection association join
-
# #compare_by_identity makes such owners different hash keys
-
1927
@records_by_owner = {}.compare_by_identity
-
1927
raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
-
-
1927
@preloaded_records = raw_records.select do |record|
-
6738
assignments = false
-
-
6738
owners_by_key[convert_key(record[association_key_name])].each do |owner|
-
7916
entries = (@records_by_owner[owner] ||= [])
-
-
7916
if reflection.collection? || entries.empty?
-
7100
entries << record
-
7100
assignments = true
-
end
-
end
-
-
6738
assignments
-
end
-
end
-
-
# The name of the key on the associated records
-
3
def association_key_name
-
17315
reflection.join_primary_key(klass)
-
end
-
-
# The name of the key on the model which declares the association
-
3
def owner_key_name
-
204810
reflection.join_foreign_key
-
end
-
-
3
def associate_records_to_owner(owner, records)
-
203933
association = owner.association(reflection.name)
-
203933
if reflection.collection?
-
3934
association.target = records
-
else
-
199999
association.target = records.first
-
end
-
end
-
-
3
def owner_keys
-
3839
@owner_keys ||= owners_by_key.keys
-
end
-
-
3
def owners_by_key
-
15403
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
-
202883
key = convert_key(owner[owner_key_name])
-
202883
(result[key] ||= []) << owner if key
-
end
-
end
-
-
3
def key_conversion_required?
-
216359
unless defined?(@key_conversion_required)
-
1927
@key_conversion_required = (association_key_type != owner_key_type)
-
end
-
-
216359
@key_conversion_required
-
end
-
-
3
def convert_key(key)
-
216359
if key_conversion_required?
-
18
key.to_s
-
else
-
216341
key
-
end
-
end
-
-
3
def association_key_type
-
1927
@klass.type_for_attribute(association_key_name).type
-
end
-
-
3
def owner_key_type
-
1927
@model.type_for_attribute(owner_key_name).type
-
end
-
-
3
def records_for(ids)
-
1912
scope.where(association_key_name => ids).load do |record|
-
# Processing only the first owner
-
# because the record is modified but not an owner
-
6738
owner = owners_by_key[convert_key(record[association_key_name])].first
-
6738
association = owner.association(reflection.name)
-
6738
association.set_inverse_instance(record)
-
end
-
end
-
-
3
def scope
-
5476
@scope ||= build_scope
-
end
-
-
3
def reflection_scope
-
3814
@reflection_scope ||= begin
-
2434
reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
-
end
-
end
-
-
3
def build_scope
-
2434
scope = klass.scope_for_association
-
-
2434
if reflection.type && !reflection.through_reflection?
-
93
scope.where!(reflection.type => model.polymorphic_name)
-
end
-
-
2434
scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
-
-
2434
if preload_scope && !preload_scope.empty_scope?
-
219
scope.merge!(preload_scope)
-
end
-
-
2434
if preload_scope && preload_scope.strict_loading_value
-
3
scope.strict_loading
-
else
-
2431
scope
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class Preloader
-
3
class ThroughAssociation < Association # :nodoc:
-
3
PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false)
-
-
3
def initialize(*)
-
522
super
-
522
@already_loaded = owners.first.association(through_reflection.name).loaded?
-
end
-
-
3
def preloaded_records
-
132
@preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
-
end
-
-
3
def records_by_owner
-
582
return @records_by_owner if defined?(@records_by_owner)
-
522
source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
-
522
through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
-
-
522
@records_by_owner = owners.each_with_object({}) do |owner, result|
-
1521
through_records = through_records_by_owner[owner] || []
-
-
1521
if @already_loaded
-
42
if source_type = reflection.options[:source_type]
-
6
through_records = through_records.select do |record|
-
12
record[reflection.foreign_type] == source_type
-
end
-
end
-
end
-
-
1521
records = through_records.flat_map do |record|
-
1790
source_records_by_owner[record]
-
end
-
-
1521
records.compact!
-
1689
records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
-
1521
records.uniq! if scope.distinct_value
-
1521
result[owner] = records
-
end
-
end
-
-
3
private
-
3
def source_preloaders
-
654
@source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
-
end
-
-
3
def middle_records
-
522
through_preloaders.flat_map(&:preloaded_records)
-
end
-
-
3
def through_preloaders
-
1044
@through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
-
end
-
-
3
def through_reflection
-
1566
reflection.through_reflection
-
end
-
-
3
def source_reflection
-
636
reflection.source_reflection
-
end
-
-
3
def preload_index
-
168
@preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
-
144
result[record] = index
-
end
-
end
-
-
3
def through_scope
-
522
scope = through_reflection.klass.unscoped
-
522
options = reflection.options
-
-
522
values = reflection_scope.values
-
522
if annotations = values[:annotate]
-
3
scope.annotate!(*annotations)
-
end
-
-
522
if options[:source_type]
-
33
scope.where! reflection.foreign_type => options[:source_type]
-
489
elsif !reflection_scope.where_clause.empty?
-
75
scope.where_clause = reflection_scope.where_clause
-
-
75
if includes = values[:includes]
-
12
scope.includes!(source_reflection.name => includes)
-
else
-
63
scope.includes!(source_reflection.name)
-
end
-
-
75
if values[:references] && !values[:references].empty?
-
39
scope.references_values |= values[:references]
-
else
-
36
scope.references!(source_reflection.table_name)
-
end
-
-
75
if joins = values[:joins]
-
scope.joins!(source_reflection.name => joins)
-
end
-
-
75
if left_outer_joins = values[:left_outer_joins]
-
3
scope.left_outer_joins!(source_reflection.name => left_outer_joins)
-
end
-
-
75
if scope.eager_loading? && order_values = values[:order]
-
6
scope = scope.order(order_values)
-
end
-
end
-
-
522
scope
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
3
class SingularAssociation < Association #:nodoc:
-
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
-
3
def reader
-
8608
if !loaded? || stale_target?
-
1976
reload
-
end
-
-
8590
target
-
end
-
-
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
-
3
def writer(record)
-
2251
replace(record)
-
end
-
-
3
def build(attributes = nil, &block)
-
297
record = build_record(attributes, &block)
-
282
set_new_record(record)
-
282
record
-
end
-
-
# Implements the reload reader method, e.g. foo.reload_bar for
-
# Foo.has_one :bar
-
3
def force_reload_reader
-
15
reload(true)
-
15
target
-
end
-
-
3
private
-
3
def scope_for_create
-
753
super.except!(klass.primary_key)
-
end
-
-
3
def find_target
-
2125
super.first
-
end
-
-
3
def replace(record)
-
raise NotImplementedError, "Subclasses must implement a replace(record) method"
-
end
-
-
3
def set_new_record(record)
-
246
replace(record)
-
end
-
-
3
def _create_record(attributes, raise_error = false, &block)
-
465
record = build_record(attributes, &block)
-
462
saved = record.save
-
462
set_new_record(record)
-
462
raise RecordInvalid.new(record) if !saved && raise_error
-
456
record
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Associations
-
# = Active Record Through Association
-
3
module ThroughAssociation #:nodoc:
-
3
delegate :source_reflection, to: :reflection
-
-
3
private
-
3
def through_reflection
-
10526
@through_reflection ||= begin
-
3557
refl = reflection.through_reflection
-
-
3557
while refl.through_reflection?
-
141
refl = refl.through_reflection
-
end
-
-
3557
refl
-
end
-
end
-
-
3
def through_association
-
6896
@through_association ||= owner.association(through_reflection.name)
-
end
-
-
# We merge in these scopes for two reasons:
-
#
-
# 1. To get the default_scope conditions for any of the other reflections in the chain
-
# 2. To get the type conditions for any STI models in the chain
-
3
def target_scope
-
4692
scope = super
-
4692
reflection.chain.drop(1).each do |reflection|
-
4899
relation = reflection.klass.scope_for_association
-
4899
scope.merge!(
-
relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
-
)
-
end
-
4692
scope
-
end
-
-
# Construct attributes for :through pointing to owner and associate. This is used by the
-
# methods which create and delete records on the association.
-
#
-
# We only support indirectly modifying through associations which have a belongs_to source.
-
# This is the "has_many :tags, through: :taggings" situation, where the join model
-
# typically has a belongs_to on both side. In other words, associations which could also
-
# be represented as has_and_belongs_to_many associations.
-
#
-
# We do not support creating/deleting records on the association where the source has
-
# some other type, because this opens up a whole can of worms, and in basically any
-
# situation it is more natural for the user to just create or modify their join records
-
# directly as required.
-
3
def construct_join_attributes(*records)
-
871
ensure_mutable
-
-
856
association_primary_key = source_reflection.association_primary_key(reflection.klass)
-
-
856
if association_primary_key == reflection.klass.primary_key && !options[:source_type]
-
808
join_attributes = { source_reflection.name => records }
-
else
-
48
join_attributes = {
-
source_reflection.foreign_key => records.map(&association_primary_key.to_sym)
-
}
-
end
-
-
856
if options[:source_type]
-
39
join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
-
end
-
-
856
if records.count == 1
-
751
join_attributes.transform_values!(&:first)
-
else
-
105
join_attributes
-
end
-
end
-
-
# Note: this does not capture all cases, for example it would be crazy to try to
-
# properly support stale-checking for nested associations.
-
3
def stale_state
-
6341
if through_reflection.belongs_to?
-
240
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
-
end
-
end
-
-
3
def foreign_key_present?
-
387
through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
-
end
-
-
3
def ensure_mutable
-
1955
unless source_reflection.belongs_to?
-
21
if reflection.has_one?
-
raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
-
else
-
21
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
-
end
-
end
-
end
-
-
3
def ensure_not_nested
-
2573
if reflection.nested?
-
24
if reflection.has_one?
-
3
raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
-
else
-
21
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
-
end
-
end
-
end
-
-
3
def build_record(attributes)
-
744
inverse = source_reflection.inverse_of
-
744
target = through_association.target
-
-
744
if inverse && target && !target.is_a?(Array)
-
3
attributes[inverse.foreign_key] = target.id
-
end
-
-
744
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_model/forbidden_attributes_protection"
-
-
3
module ActiveRecord
-
3
module AttributeAssignment
-
3
include ActiveModel::AttributeAssignment
-
-
3
private
-
3
def _assign_attributes(attributes)
-
16818
multi_parameter_attributes = nested_parameter_attributes = nil
-
-
16818
attributes.each do |k, v|
-
22064
key = k.to_s
-
-
22064
if key.include?("(")
-
543
(multi_parameter_attributes ||= {})[key] = v
-
21521
elsif v.is_a?(Hash)
-
457
(nested_parameter_attributes ||= {})[key] = v
-
else
-
21064
_assign_attribute(key, v)
-
end
-
end
-
-
16770
assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
-
16755
assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
-
end
-
-
# Assign any deferred nested attributes after the base attributes have been set.
-
3
def assign_nested_parameter_attributes(pairs)
-
914
pairs.each { |k, v| _assign_attribute(k, v) }
-
end
-
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
-
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
-
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
-
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
-
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
-
3
def assign_multiparameter_attributes(pairs)
-
138
execute_callstack_for_multiparameter_attributes(
-
extract_callstack_for_multiparameter_attributes(pairs)
-
)
-
end
-
-
3
def execute_callstack_for_multiparameter_attributes(callstack)
-
138
errors = []
-
138
callstack.each do |name, values_with_empty_parameters|
-
138
if values_with_empty_parameters.each_value.all?(&:nil?)
-
3
values = nil
-
else
-
135
values = values_with_empty_parameters
-
end
-
138
send("#{name}=", values)
-
rescue => ex
-
24
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
-
end
-
138
unless errors.empty?
-
24
error_descriptions = errors.map(&:message).join(",")
-
24
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
-
end
-
end
-
-
3
def extract_callstack_for_multiparameter_attributes(pairs)
-
138
attributes = {}
-
-
138
pairs.each do |(multiparameter_name, value)|
-
543
attribute_name = multiparameter_name.split("(").first
-
543
attributes[attribute_name] ||= {}
-
-
543
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
-
543
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
-
end
-
-
138
attributes
-
end
-
-
3
def type_cast_attribute_value(multiparameter_name, value)
-
453
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
-
end
-
-
3
def find_parameter_position(multiparameter_name)
-
543
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "mutex_m"
-
3
require "active_support/core_ext/enumerable"
-
-
3
module ActiveRecord
-
# = Active Record Attribute Methods
-
3
module AttributeMethods
-
3
extend ActiveSupport::Concern
-
3
include ActiveModel::AttributeMethods
-
-
3
included do
-
9
initialize_generated_modules
-
9
include Read
-
9
include Write
-
9
include BeforeTypeCast
-
9
include Query
-
9
include PrimaryKey
-
9
include TimeZoneConversion
-
9
include Dirty
-
9
include Serialization
-
end
-
-
3
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
-
-
3
class GeneratedAttributeMethods < Module #:nodoc:
-
3
include Mutex_m
-
end
-
-
3
class << self
-
3
def dangerous_attribute_methods # :nodoc:
-
428652
@dangerous_attribute_methods ||= (
-
Base.instance_methods +
-
Base.private_instance_methods -
-
3
Base.superclass.instance_methods -
-
Base.superclass.private_instance_methods
-
1026
).map { |m| -m.to_s }.to_set.freeze
-
end
-
end
-
-
3
module ClassMethods
-
3
def inherited(child_class) #:nodoc:
-
2940
child_class.initialize_generated_modules
-
2940
super
-
end
-
-
3
def initialize_generated_modules # :nodoc:
-
2949
@generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
-
2949
private_constant :GeneratedAttributeMethods
-
2949
@attribute_methods_generated = false
-
2949
include @generated_attribute_methods
-
-
2949
super
-
end
-
-
# Generates all the attribute related methods for columns in the database
-
# accessors, mutators and query methods.
-
3
def define_attribute_methods # :nodoc:
-
262466
return false if @attribute_methods_generated
-
# Use a mutex; we don't want two threads simultaneously trying to define
-
# attribute methods.
-
2752
generated_attribute_methods.synchronize do
-
2752
return false if @attribute_methods_generated
-
2749
superclass.define_attribute_methods unless base_class?
-
2749
super(attribute_names)
-
2749
@attribute_methods_generated = true
-
end
-
end
-
-
3
def undefine_attribute_methods # :nodoc:
-
3779
generated_attribute_methods.synchronize do
-
3779
super if defined?(@attribute_methods_generated) && @attribute_methods_generated
-
3779
@attribute_methods_generated = false
-
end
-
end
-
-
# Raises an ActiveRecord::DangerousAttributeError exception when an
-
# \Active \Record method is defined in the model, otherwise +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# def save
-
# 'already defined by Active Record'
-
# end
-
# end
-
#
-
# Person.instance_method_already_implemented?(:save)
-
# # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
-
#
-
# Person.instance_method_already_implemented?(:name)
-
# # => false
-
3
def instance_method_already_implemented?(method_name)
-
424449
if dangerous_attribute_method?(method_name)
-
6
raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
-
end
-
-
424443
if superclass == Base
-
295900
super
-
else
-
# If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
-
# defines its own attribute method, then we don't want to overwrite that.
-
128543
defined = method_defined_within?(method_name, superclass, Base) &&
-
! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
-
128543
defined || super
-
end
-
end
-
-
# A method name is 'dangerous' if it is already (re)defined by Active Record, but
-
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
-
3
def dangerous_attribute_method?(name) # :nodoc:
-
428652
::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(name.to_s)
-
end
-
-
3
def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
-
130673
if klass.method_defined?(name) || klass.private_method_defined?(name)
-
121962
if superklass.method_defined?(name) || superklass.private_method_defined?(name)
-
3633
klass.instance_method(name).owner != superklass.instance_method(name).owner
-
else
-
118329
true
-
end
-
else
-
8711
false
-
end
-
end
-
-
# A class method is 'dangerous' if it is already (re)defined by Active Record, but
-
# not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
-
3
def dangerous_class_method?(method_name)
-
1422
return true if RESTRICTED_CLASS_METHODS.include?(method_name.to_s)
-
-
1359
if Base.respond_to?(method_name, true)
-
33
if Object.respond_to?(method_name, true)
-
12
Base.method(method_name).owner != Object.method(method_name).owner
-
else
-
21
true
-
end
-
else
-
1326
false
-
end
-
end
-
-
# Returns +true+ if +attribute+ is an attribute method and table exists,
-
# +false+ otherwise.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# Person.attribute_method?('name') # => true
-
# Person.attribute_method?(:age=) # => true
-
# Person.attribute_method?(:nothing) # => false
-
3
def attribute_method?(attribute)
-
18
super || (table_exists? && column_names.include?(attribute.to_s.delete_suffix("=")))
-
end
-
-
# Returns an array of column names as strings if it's not an abstract class and
-
# table exists. Otherwise it returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# Person.attribute_names
-
# # => ["id", "created_at", "updated_at", "name", "age"]
-
3
def attribute_names
-
3094
@attribute_names ||= if !abstract_class? && table_exists?
-
2719
attribute_types.keys
-
else
-
15
[]
-
end.freeze
-
end
-
-
# Returns true if the given attribute exists, otherwise false.
-
#
-
# class Person < ActiveRecord::Base
-
# alias_attribute :new_name, :name
-
# end
-
#
-
# Person.has_attribute?('name') # => true
-
# Person.has_attribute?('new_name') # => true
-
# Person.has_attribute?(:age) # => true
-
# Person.has_attribute?(:nothing) # => false
-
3
def has_attribute?(attr_name)
-
352
attr_name = attr_name.to_s
-
352
attr_name = attribute_aliases[attr_name] || attr_name
-
352
attribute_types.key?(attr_name)
-
end
-
-
3
def _has_attribute?(attr_name) # :nodoc:
-
35542
attribute_types.key?(attr_name)
-
end
-
end
-
-
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
-
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
-
# which will all return +true+. It also defines the attribute methods if they have
-
# not been generated.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person.respond_to?(:name) # => true
-
# person.respond_to?(:name=) # => true
-
# person.respond_to?(:name?) # => true
-
# person.respond_to?('age') # => true
-
# person.respond_to?('age=') # => true
-
# person.respond_to?('age?') # => true
-
# person.respond_to?(:nothing) # => false
-
3
def respond_to?(name, include_private = false)
-
27515
return false unless super
-
-
# If the result is true then check for the select case.
-
# For queries selecting a subset of columns, return false for unselected columns.
-
# We check defined?(@attributes) not to issue warnings if called on objects that
-
# have been allocated but not yet initialized.
-
24730
if defined?(@attributes)
-
24662
if name = self.class.symbol_column_to_string(name.to_sym)
-
60
return _has_attribute?(name)
-
end
-
end
-
-
24670
true
-
end
-
-
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# alias_attribute :new_name, :name
-
# end
-
#
-
# person = Person.new
-
# person.has_attribute?(:name) # => true
-
# person.has_attribute?(:new_name) # => true
-
# person.has_attribute?('age') # => true
-
# person.has_attribute?(:nothing) # => false
-
3
def has_attribute?(attr_name)
-
146
attr_name = attr_name.to_s
-
146
attr_name = self.class.attribute_aliases[attr_name] || attr_name
-
146
@attributes.key?(attr_name)
-
end
-
-
3
def _has_attribute?(attr_name) # :nodoc:
-
20662
@attributes.key?(attr_name)
-
end
-
-
# Returns an array of names for the attributes available on this object.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person.attribute_names
-
# # => ["id", "created_at", "updated_at", "name", "age"]
-
3
def attribute_names
-
1685
@attributes.keys
-
end
-
-
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.create(name: 'Francesco', age: 22)
-
# person.attributes
-
# # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
-
3
def attributes
-
816
@attributes.to_hash
-
end
-
-
# Returns an <tt>#inspect</tt>-like string for the value of the
-
# attribute +attr_name+. String attributes are truncated up to 50
-
# characters, Date and Time attributes are returned in the
-
# <tt>:db</tt> format. Other attributes return the value of
-
# <tt>#inspect</tt> without modification.
-
#
-
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
-
#
-
# person.attribute_for_inspect(:name)
-
# # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
-
#
-
# person.attribute_for_inspect(:created_at)
-
# # => "\"2012-10-22 00:15:07\""
-
#
-
# person.attribute_for_inspect(:tag_ids)
-
# # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
-
3
def attribute_for_inspect(attr_name)
-
20
attr_name = attr_name.to_s
-
20
attr_name = self.class.attribute_aliases[attr_name] || attr_name
-
20
value = _read_attribute(attr_name)
-
20
format_for_inspect(value)
-
end
-
-
# Returns +true+ if the specified +attribute+ has been set by the user or by a
-
# database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
-
# to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
-
# Note that it always returns +true+ with boolean attributes.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(title: '', is_done: false)
-
# task.attribute_present?(:title) # => false
-
# task.attribute_present?(:is_done) # => true
-
# task.title = 'Buy milk'
-
# task.is_done = true
-
# task.attribute_present?(:title) # => true
-
# task.attribute_present?(:is_done) # => true
-
3
def attribute_present?(attr_name)
-
2042
attr_name = attr_name.to_s
-
2042
attr_name = self.class.attribute_aliases[attr_name] || attr_name
-
2042
value = _read_attribute(attr_name)
-
2042
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
-
end
-
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
-
# "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
-
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
-
#
-
# Note: +:id+ is always present.
-
#
-
# class Person < ActiveRecord::Base
-
# belongs_to :organization
-
# end
-
#
-
# person = Person.new(name: 'Francesco', age: '22')
-
# person[:name] # => "Francesco"
-
# person[:age] # => 22
-
#
-
# person = Person.select('id').first
-
# person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
-
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
-
3
def [](attr_name)
-
246684
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
-
end
-
-
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
-
# (Alias for the protected #write_attribute method).
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person[:age] = '22'
-
# person[:age] # => 22
-
# person[:age].class # => Integer
-
3
def []=(attr_name, value)
-
21079
write_attribute(attr_name, value)
-
end
-
-
# Returns the name of all database fields which have been read from this
-
# model. This can be useful in development mode to determine which fields
-
# need to be selected. For performance critical pages, selecting only the
-
# required fields can be an easy performance win (assuming you aren't using
-
# all of the fields on the model).
-
#
-
# For example:
-
#
-
# class PostsController < ActionController::Base
-
# after_action :print_accessed_fields, only: :index
-
#
-
# def index
-
# @posts = Post.all
-
# end
-
#
-
# private
-
#
-
# def print_accessed_fields
-
# p @posts.first.accessed_fields
-
# end
-
# end
-
#
-
# Which allows you to quickly change your code to:
-
#
-
# class PostsController < ActionController::Base
-
# def index
-
# @posts = Post.select(:id, :title, :author_id, :updated_at)
-
# end
-
# end
-
3
def accessed_fields
-
6
@attributes.accessed
-
end
-
-
3
private
-
3
def attribute_method?(attr_name)
-
# We check defined? because Syck calls respond_to? before actually calling initialize.
-
812
defined?(@attributes) && @attributes.key?(attr_name)
-
end
-
-
3
def attributes_with_values(attribute_names)
-
14836
attribute_names.index_with do |name|
-
35096
_read_attribute(name)
-
end
-
end
-
-
# Filters the primary keys and readonly attributes from the attribute names.
-
3
def attributes_for_update(attribute_names)
-
3413
attribute_names &= self.class.column_names
-
3413
attribute_names.delete_if do |name|
-
3137
self.class.readonly_attribute?(name)
-
end
-
end
-
-
# Filters out the primary keys, from the attribute names, when the primary
-
# key is to be generated (e.g. the id attribute has no value).
-
3
def attributes_for_create(attribute_names)
-
12437
attribute_names &= self.class.column_names
-
12437
attribute_names.delete_if do |name|
-
31064
pk_attribute?(name) && id.nil?
-
end
-
end
-
-
3
def format_for_inspect(value)
-
2241
if value.is_a?(String) && value.length > 50
-
6
"#{value[0, 50]}...".inspect
-
2235
elsif value.is_a?(Date) || value.is_a?(Time)
-
96
%("#{value.to_s(:inspect)}")
-
else
-
2139
value.inspect
-
end
-
end
-
-
3
def pk_attribute?(name)
-
31064
name == @primary_key
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
# = Active Record Attribute Methods Before Type Cast
-
#
-
# ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to
-
# read the value of the attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.id # => 1
-
# task.completed_on # => Sun, 21 Oct 2012
-
#
-
# task.attributes_before_type_cast
-
# # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
-
# task.read_attribute_before_type_cast('id') # => "1"
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
#
-
# In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
-
# it declares a method for all attributes with the <tt>*_before_type_cast</tt>
-
# suffix.
-
#
-
# task.id_before_type_cast # => "1"
-
# task.completed_on_before_type_cast # => "2012-10-21"
-
3
module BeforeTypeCast
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
9
attribute_method_suffix "_before_type_cast"
-
9
attribute_method_suffix "_came_from_user?"
-
end
-
-
# Returns the value of the attribute identified by +attr_name+ before
-
# typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.read_attribute('id') # => 1
-
# task.read_attribute_before_type_cast('id') # => '1'
-
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
-
3
def read_attribute_before_type_cast(attr_name)
-
514
attribute_before_type_cast(attr_name.to_s)
-
end
-
-
# Returns a hash of attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
-
# task.attributes
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
-
# task.attributes_before_type_cast
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
-
3
def attributes_before_type_cast
-
3
@attributes.values_before_type_cast
-
end
-
-
3
private
-
# Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
-
3
def attribute_before_type_cast(attr_name)
-
683
@attributes[attr_name].value_before_type_cast
-
end
-
-
3
def attribute_came_from_user?(attr_name)
-
141
@attributes[attr_name].came_from_user?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/module/attribute_accessors"
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
3
module Dirty
-
3
extend ActiveSupport::Concern
-
-
3
include ActiveModel::Dirty
-
-
3
included do
-
9
if self < ::ActiveRecord::Timestamp
-
raise "You cannot include Dirty after Timestamp"
-
end
-
-
9
class_attribute :partial_writes, instance_writer: false, default: true
-
-
# Attribute methods for "changed in last call to save?"
-
9
attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
-
9
attribute_method_prefix("saved_change_to_")
-
9
attribute_method_suffix("_before_last_save")
-
-
# Attribute methods for "will change if I call save?"
-
9
attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
-
9
attribute_method_suffix("_change_to_be_saved", "_in_database")
-
end
-
-
# <tt>reload</tt> the record and clears changed attributes.
-
3
def reload(*)
-
2233
super.tap do
-
2215
@mutations_before_last_save = nil
-
2215
@mutations_from_database = nil
-
end
-
end
-
-
# Did this attribute change when we last saved?
-
#
-
# This method is useful in after callbacks to determine if an attribute
-
# was changed during the save that triggered the callbacks to run. It can
-
# be invoked as +saved_change_to_name?+ instead of
-
# <tt>saved_change_to_attribute?("name")</tt>.
-
#
-
# ==== Options
-
#
-
# +from+ When passed, this method will return false unless the original
-
# value is equal to the given option
-
#
-
# +to+ When passed, this method will return false unless the value was
-
# changed to the given value
-
3
def saved_change_to_attribute?(attr_name, **options)
-
577
mutations_before_last_save.changed?(attr_name.to_s, **options)
-
end
-
-
# Returns the change to an attribute during the last save. If the
-
# attribute was changed, the result will be an array containing the
-
# original value and the saved value.
-
#
-
# This method is useful in after callbacks, to see the change in an
-
# attribute during the save that triggered the callbacks to run. It can be
-
# invoked as +saved_change_to_name+ instead of
-
# <tt>saved_change_to_attribute("name")</tt>.
-
3
def saved_change_to_attribute(attr_name)
-
36
mutations_before_last_save.change_to_attribute(attr_name.to_s)
-
end
-
-
# Returns the original value of an attribute before the last save.
-
#
-
# This method is useful in after callbacks to get the original value of an
-
# attribute before the save that triggered the callbacks to run. It can be
-
# invoked as +name_before_last_save+ instead of
-
# <tt>attribute_before_last_save("name")</tt>.
-
3
def attribute_before_last_save(attr_name)
-
159
mutations_before_last_save.original_value(attr_name.to_s)
-
end
-
-
# Did the last call to +save+ have any changes to change?
-
3
def saved_changes?
-
633
mutations_before_last_save.any_changes?
-
end
-
-
# Returns a hash containing all the changes that were just saved.
-
3
def saved_changes
-
536
mutations_before_last_save.changes
-
end
-
-
# Will this attribute change the next time we save?
-
#
-
# This method is useful in validations and before callbacks to determine
-
# if the next call to +save+ will change a particular attribute. It can be
-
# invoked as +will_save_change_to_name?+ instead of
-
# <tt>will_save_change_to_attribute?("name")</tt>.
-
#
-
# ==== Options
-
#
-
# +from+ When passed, this method will return false unless the original
-
# value is equal to the given option
-
#
-
# +to+ When passed, this method will return false unless the value will be
-
# changed to the given value
-
3
def will_save_change_to_attribute?(attr_name, **options)
-
2627
mutations_from_database.changed?(attr_name.to_s, **options)
-
end
-
-
# Returns the change to an attribute that will be persisted during the
-
# next save.
-
#
-
# This method is useful in validations and before callbacks, to see the
-
# change to an attribute that will occur when the record is saved. It can
-
# be invoked as +name_change_to_be_saved+ instead of
-
# <tt>attribute_change_to_be_saved("name")</tt>.
-
#
-
# If the attribute will change, the result will be an array containing the
-
# original value and the new value about to be saved.
-
3
def attribute_change_to_be_saved(attr_name)
-
6
mutations_from_database.change_to_attribute(attr_name.to_s)
-
end
-
-
# Returns the value of an attribute in the database, as opposed to the
-
# in-memory value that will be persisted the next time the record is
-
# saved.
-
#
-
# This method is useful in validations and before callbacks, to see the
-
# original value of an attribute prior to any changes about to be
-
# saved. It can be invoked as +name_in_database+ instead of
-
# <tt>attribute_in_database("name")</tt>.
-
3
def attribute_in_database(attr_name)
-
4591
mutations_from_database.original_value(attr_name.to_s)
-
end
-
-
# Will the next call to +save+ have any changes to persist?
-
3
def has_changes_to_save?
-
11007
mutations_from_database.any_changes?
-
end
-
-
# Returns a hash containing all the changes that will be persisted during
-
# the next save.
-
3
def changes_to_save
-
138
mutations_from_database.changes
-
end
-
-
# Returns an array of the names of any attributes that will change when
-
# the record is next saved.
-
3
def changed_attribute_names_to_save
-
20918
mutations_from_database.changed_attribute_names
-
end
-
-
# Returns a hash of the attributes that will change when the record is
-
# next saved.
-
#
-
# The hash keys are the attribute names, and the hash values are the
-
# original attribute values in the database (as opposed to the in-memory
-
# values about to be saved).
-
3
def attributes_in_database
-
6
mutations_from_database.changed_values
-
end
-
-
3
private
-
3
def write_attribute_without_type_cast(attr_name, value)
-
449
result = super
-
446
clear_attribute_change(attr_name)
-
446
result
-
end
-
-
3
def _touch_row(attribute_names, time)
-
504
@_touch_attr_names = Set.new(attribute_names)
-
-
504
affected_rows = super
-
-
489
if @_skip_dirty_tracking ||= false
-
342
clear_attribute_changes(@_touch_attr_names)
-
342
return affected_rows
-
end
-
-
147
changes = {}
-
147
@attributes.keys.each do |attr_name|
-
1803
next if @_touch_attr_names.include?(attr_name)
-
-
1593
if attribute_changed?(attr_name)
-
3
changes[attr_name] = _read_attribute(attr_name)
-
3
_write_attribute(attr_name, attribute_was(attr_name))
-
3
clear_attribute_change(attr_name)
-
end
-
end
-
-
147
changes_applied
-
150
changes.each { |attr_name, value| _write_attribute(attr_name, value) }
-
-
147
affected_rows
-
ensure
-
504
@_touch_attr_names, @_skip_dirty_tracking = nil, nil
-
end
-
-
3
def _update_record(attribute_names = attribute_names_for_partial_writes)
-
3413
affected_rows = super
-
3372
changes_applied
-
3372
affected_rows
-
end
-
-
3
def _create_record(attribute_names = attribute_names_for_partial_writes)
-
12437
id = super
-
12384
changes_applied
-
12384
id
-
end
-
-
3
def attribute_names_for_partial_writes
-
15857
partial_writes? ? changed_attribute_names_to_save : attribute_names
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "set"
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
3
module PrimaryKey
-
3
extend ActiveSupport::Concern
-
-
# Returns this record's primary key value wrapped in an array if one is
-
# available.
-
3
def to_key
-
100
key = id
-
100
[key] if key
-
end
-
-
# Returns the primary key column's value.
-
3
def id
-
155721
_read_attribute(@primary_key)
-
end
-
-
# Sets the primary key column's value.
-
3
def id=(value)
-
11242
_write_attribute(@primary_key, value)
-
end
-
-
# Queries the primary key column's value.
-
3
def id?
-
query_attribute(@primary_key)
-
end
-
-
# Returns the primary key column's value before type cast.
-
3
def id_before_type_cast
-
3
read_attribute_before_type_cast(@primary_key)
-
end
-
-
# Returns the primary key column's previous value.
-
3
def id_was
-
attribute_was(@primary_key)
-
end
-
-
# Returns the primary key column's value from the database.
-
3
def id_in_database
-
4103
attribute_in_database(@primary_key)
-
end
-
-
3
def id_for_database # :nodoc:
-
24
@attributes[@primary_key].value_for_database
-
end
-
-
3
private
-
3
def attribute_method?(attr_name)
-
815
attr_name == "id" || super
-
end
-
-
3
module ClassMethods
-
3
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
-
-
3
def instance_method_already_implemented?(method_name)
-
424449
super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
-
end
-
-
3
def dangerous_attribute_method?(method_name)
-
428652
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
-
end
-
-
# Defines the primary key field -- can be overridden in subclasses.
-
# Overwriting will negate any effect of the +primary_key_prefix_type+
-
# setting, though.
-
3
def primary_key
-
802349
@primary_key = reset_primary_key unless defined? @primary_key
-
802343
@primary_key
-
end
-
-
# Returns a quoted version of the primary key name, used to construct
-
# SQL statements.
-
3
def quoted_primary_key
-
21
@quoted_primary_key ||= connection.quote_column_name(primary_key)
-
end
-
-
3
def reset_primary_key #:nodoc:
-
2086
if base_class?
-
1597
self.primary_key = get_primary_key(base_class.name)
-
else
-
489
self.primary_key = base_class.primary_key
-
end
-
end
-
-
3
def get_primary_key(base_name) #:nodoc:
-
4865
if base_name && primary_key_prefix_type == :table_name
-
6
base_name.foreign_key(false)
-
4859
elsif base_name && primary_key_prefix_type == :table_name_with_underscore
-
6
base_name.foreign_key
-
else
-
4853
if ActiveRecord::Base != self && table_exists?
-
1567
pk = connection.schema_cache.primary_keys(table_name)
-
1567
suppress_composite_primary_key(pk)
-
else
-
3280
"id"
-
end
-
end
-
end
-
-
# Sets the name of the primary key column.
-
#
-
# class Project < ActiveRecord::Base
-
# self.primary_key = 'sysid'
-
# end
-
#
-
# You can also define the #primary_key method yourself:
-
#
-
# class Project < ActiveRecord::Base
-
# def self.primary_key
-
# 'foo_' + super
-
# end
-
# end
-
#
-
# Project.primary_key # => "foo_id"
-
3
def primary_key=(value)
-
2143
@primary_key = value && -value.to_s
-
2143
@quoted_primary_key = nil
-
2143
@attributes_builder = nil
-
end
-
-
3
private
-
3
def suppress_composite_primary_key(pk)
-
1408
return pk unless pk.is_a?(Array)
-
-
3
warn <<~WARNING
-
WARNING: Active Record does not support composite primary key.
-
-
#{table_name} has composite primary key. Composite primary key is ignored.
-
WARNING
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
3
module Query
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
9
attribute_method_suffix "?"
-
end
-
-
3
def query_attribute(attr_name)
-
687
value = self[attr_name]
-
-
684
case value
-
213
when true then true
-
308
when false, nil then false
-
else
-
179
if !type_for_attribute(attr_name) { false }
-
16
if Numeric === value || !value.match?(/[^0-9]/)
-
10
!value.to_i.zero?
-
else
-
6
return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
-
6
!value.blank?
-
end
-
147
elsif value.respond_to?(:zero?)
-
12
!value.zero?
-
else
-
135
!value.blank?
-
end
-
end
-
end
-
-
3
alias :attribute? :query_attribute
-
3
private :attribute?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
3
module Read
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods # :nodoc:
-
3
private
-
3
def define_method_attribute(name, owner:)
-
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
-
owner, name
-
18732
) do |temp_method_name, attr_name_expr|
-
owner <<
-
"def #{temp_method_name}" <<
-
18732
" _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
-
"end"
-
end
-
end
-
end
-
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after
-
# it has been typecast (for example, "2004-12-12" in a date column is cast
-
# to a date object, like Date.new(2004, 12, 12)).
-
3
def read_attribute(attr_name, &block)
-
247105
name = attr_name.to_s
-
247105
name = self.class.attribute_aliases[name] || name
-
-
247105
name = @primary_key if name == "id" && @primary_key
-
247105
@attributes.fetch_value(name, &block)
-
end
-
-
# This method exists to avoid the expensive primary_key check internally, without
-
# breaking compatibility with the read_attribute API
-
3
def _read_attribute(attr_name, &block) # :nodoc
-
530269
@attributes.fetch_value(attr_name, &block)
-
end
-
-
3
alias :attribute :_read_attribute
-
3
private :attribute
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
3
module Serialization
-
3
extend ActiveSupport::Concern
-
-
3
class ColumnNotSerializableError < StandardError
-
3
def initialize(name, type)
-
8
super <<~EOS
-
Column `#{name}` of type #{type.class} does not support `serialize` feature.
-
Usually it means that you are trying to use `serialize`
-
on a column that already implements serialization natively.
-
EOS
-
end
-
end
-
-
3
module ClassMethods
-
# If you have an attribute that needs to be saved to the database as an
-
# object, and retrieved as the same object, then specify the name of that
-
# attribute using this method and it will be handled automatically. The
-
# serialization is done through YAML. If +class_name+ is specified, the
-
# serialized object must be of that class on assignment and retrieval.
-
# Otherwise SerializationTypeMismatch will be raised.
-
#
-
# Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
-
# +Array+, will always be persisted as null.
-
#
-
# Keep in mind that database adapters handle certain serialization tasks
-
# for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
-
# converted between JSON object/array syntax and Ruby +Hash+ or +Array+
-
# objects transparently. There is no need to use #serialize in this
-
# case.
-
#
-
# For more complex cases, such as conversion to or from your application
-
# domain objects, consider using the ActiveRecord::Attributes API.
-
#
-
# ==== Parameters
-
#
-
# * +attr_name+ - The field name that should be serialized.
-
# * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
-
# or a class name that the object type should be equal to.
-
#
-
# ==== Example
-
#
-
# # Serialize a preferences attribute.
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
#
-
# # Serialize preferences using JSON as coder.
-
# class User < ActiveRecord::Base
-
# serialize :preferences, JSON
-
# end
-
#
-
# # Serialize preferences as Hash using YAML coder.
-
# class User < ActiveRecord::Base
-
# serialize :preferences, Hash
-
# end
-
3
def serialize(attr_name, class_name_or_coder = Object)
-
# When ::JSON is used, force it to go through the Active Support JSON encoder
-
# to ensure special objects (e.g. Active Record models) are dumped correctly
-
# using the #as_json hook.
-
273
coder = if class_name_or_coder == ::JSON
-
22
Coders::JSON
-
548
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
-
46
class_name_or_coder
-
else
-
205
Coders::YAMLColumn.new(attr_name, class_name_or_coder)
-
end
-
-
270
decorate_attribute_type(attr_name.to_s) do |cast_type|
-
499
if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
-
8
raise ColumnNotSerializableError.new(attr_name, cast_type)
-
end
-
-
491
Type::Serialized.new(cast_type, coder)
-
end
-
end
-
-
3
private
-
3
def type_incompatible_with_serialize?(type, class_name)
-
499
type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
-
type.respond_to?(:type_cast_array, true) && class_name == ::Array
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/object/try"
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
3
module TimeZoneConversion
-
3
class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
-
3
def self.new(subtype)
-
342
self === subtype ? subtype : super
-
end
-
-
3
def deserialize(value)
-
536
convert_time_to_time_zone(super)
-
end
-
-
3
def cast(value)
-
311
return if value.nil?
-
-
305
if value.is_a?(Hash)
-
12
set_time_zone_without_conversion(super)
-
293
elsif value.respond_to?(:in_time_zone)
-
286
begin
-
286
super(user_input_in_time_zone(value)) || super
-
rescue ArgumentError
-
nil
-
end
-
else
-
14
map_avoiding_infinite_recursion(super) { |v| cast(v) }
-
end
-
end
-
-
3
private
-
3
def convert_time_to_time_zone(value)
-
543
return if value.nil?
-
-
119
if value.acts_like?(:time)
-
114
value.in_time_zone
-
5
elsif value.is_a?(::Float)
-
value
-
else
-
12
map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
-
end
-
end
-
-
3
def set_time_zone_without_conversion(value)
-
12
::Time.zone.local_to_utc(value).try(:in_time_zone) if value
-
end
-
-
3
def map_avoiding_infinite_recursion(value)
-
12
map(value) do |v|
-
17
if value.equal?(v)
-
3
nil
-
else
-
14
yield(v)
-
end
-
end
-
end
-
end
-
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
9
mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
-
-
9
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
-
9
class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
-
end
-
-
3
module ClassMethods # :nodoc:
-
3
def define_attribute(name, cast_type, **)
-
30036
if create_time_zone_conversion_attribute?(name, cast_type)
-
342
cast_type = TimeZoneConverter.new(cast_type)
-
end
-
30036
super
-
end
-
-
3
private
-
3
def create_time_zone_conversion_attribute?(name, cast_type)
-
30036
enabled_for_column = time_zone_aware_attributes &&
-
!skip_time_zone_conversion_for_attributes.include?(name.to_sym)
-
-
30036
enabled_for_column && time_zone_aware_types.include?(cast_type.type)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module AttributeMethods
-
3
module Write
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
9
attribute_method_suffix "="
-
end
-
-
3
module ClassMethods # :nodoc:
-
3
private
-
3
def define_method_attribute=(name, owner:)
-
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
-
owner, name, writer: true,
-
18623
) do |temp_method_name, attr_name_expr|
-
owner <<
-
"def #{temp_method_name}(value)" <<
-
18623
" _write_attribute(#{attr_name_expr}, value)" <<
-
"end"
-
end
-
end
-
end
-
-
# Updates the attribute identified by <tt>attr_name</tt> with the
-
# specified +value+. Empty strings for Integer and Float columns are
-
# turned into +nil+.
-
3
def write_attribute(attr_name, value)
-
21400
name = attr_name.to_s
-
21400
name = self.class.attribute_aliases[name] || name
-
-
21400
name = @primary_key if name == "id" && @primary_key
-
21400
@attributes.write_from_user(name, value)
-
end
-
-
# This method exists to avoid the expensive primary_key check internally, without
-
# breaking compatibility with the write_attribute API
-
3
def _write_attribute(attr_name, value) # :nodoc:
-
109693
@attributes.write_from_user(attr_name, value)
-
end
-
-
3
alias :attribute= :_write_attribute
-
3
private :attribute=
-
-
3
private
-
3
def write_attribute_without_type_cast(attr_name, value)
-
449
@attributes.write_cast_value(attr_name, value)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_model/attribute/user_provided_default"
-
-
3
module ActiveRecord
-
# See ActiveRecord::Attributes::ClassMethods for documentation
-
3
module Attributes
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
3
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
-
end
-
-
3
module ClassMethods
-
# Defines an attribute with a type on this model. It will override the
-
# type of existing attributes if needed. This allows control over how
-
# values are converted to and from SQL when assigned to a model. It also
-
# changes the behavior of values passed to
-
# {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
-
# your domain objects across much of Active Record, without having to
-
# rely on implementation details or monkey patching.
-
#
-
# +name+ The name of the methods to define attribute methods for, and the
-
# column which this will persist to.
-
#
-
# +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
-
# to be used for this attribute. See the examples below for more
-
# information about providing custom type objects.
-
#
-
# ==== Options
-
#
-
# The following options are accepted:
-
#
-
# +default+ The default value to use when no value is provided. If this option
-
# is not passed, the previous default value (if any) will be used.
-
# Otherwise, the default will be +nil+.
-
#
-
# +array+ (PostgreSQL only) specifies that the type should be an array (see the
-
# examples below).
-
#
-
# +range+ (PostgreSQL only) specifies that the type should be a range (see the
-
# examples below).
-
#
-
# When using a symbol for +cast_type+, extra options are forwarded to the
-
# constructor of the type object.
-
#
-
# ==== Examples
-
#
-
# The type detected by Active Record can be overridden.
-
#
-
# # db/schema.rb
-
# create_table :store_listings, force: true do |t|
-
# t.decimal :price_in_cents
-
# end
-
#
-
# # app/models/store_listing.rb
-
# class StoreListing < ActiveRecord::Base
-
# end
-
#
-
# store_listing = StoreListing.new(price_in_cents: '10.1')
-
#
-
# # before
-
# store_listing.price_in_cents # => BigDecimal(10.1)
-
#
-
# class StoreListing < ActiveRecord::Base
-
# attribute :price_in_cents, :integer
-
# end
-
#
-
# # after
-
# store_listing.price_in_cents # => 10
-
#
-
# A default can also be provided.
-
#
-
# # db/schema.rb
-
# create_table :store_listings, force: true do |t|
-
# t.string :my_string, default: "original default"
-
# end
-
#
-
# StoreListing.new.my_string # => "original default"
-
#
-
# # app/models/store_listing.rb
-
# class StoreListing < ActiveRecord::Base
-
# attribute :my_string, :string, default: "new default"
-
# end
-
#
-
# StoreListing.new.my_string # => "new default"
-
#
-
# class Product < ActiveRecord::Base
-
# attribute :my_default_proc, :datetime, default: -> { Time.now }
-
# end
-
#
-
# Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
-
# sleep 1
-
# Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
-
#
-
# \Attributes do not need to be backed by a database column.
-
#
-
# # app/models/my_model.rb
-
# class MyModel < ActiveRecord::Base
-
# attribute :my_string, :string
-
# attribute :my_int_array, :integer, array: true
-
# attribute :my_float_range, :float, range: true
-
# end
-
#
-
# model = MyModel.new(
-
# my_string: "string",
-
# my_int_array: ["1", "2", "3"],
-
# my_float_range: "[1,3.5]",
-
# )
-
# model.attributes
-
# # =>
-
# {
-
# my_string: "string",
-
# my_int_array: [1, 2, 3],
-
# my_float_range: 1.0..3.5
-
# }
-
#
-
# Passing options to the type constructor
-
#
-
# # app/models/my_model.rb
-
# class MyModel < ActiveRecord::Base
-
# attribute :small_int, :integer, limit: 2
-
# end
-
#
-
# MyModel.create(small_int: 65537)
-
# # => Error: 65537 is out of range for the limit of two bytes
-
#
-
# ==== Creating Custom Types
-
#
-
# Users may also define their own custom types, as long as they respond
-
# to the methods defined on the value type. The method +deserialize+ or
-
# +cast+ will be called on your type object, with raw input from the
-
# database or from your controllers. See ActiveModel::Type::Value for the
-
# expected API. It is recommended that your type objects inherit from an
-
# existing type, or from ActiveRecord::Type::Value
-
#
-
# class MoneyType < ActiveRecord::Type::Integer
-
# def cast(value)
-
# if !value.kind_of?(Numeric) && value.include?('$')
-
# price_in_dollars = value.gsub(/\$/, '').to_f
-
# super(price_in_dollars * 100)
-
# else
-
# super
-
# end
-
# end
-
# end
-
#
-
# # config/initializers/types.rb
-
# ActiveRecord::Type.register(:money, MoneyType)
-
#
-
# # app/models/store_listing.rb
-
# class StoreListing < ActiveRecord::Base
-
# attribute :price_in_cents, :money
-
# end
-
#
-
# store_listing = StoreListing.new(price_in_cents: '$10.00')
-
# store_listing.price_in_cents # => 1000
-
#
-
# For more details on creating custom types, see the documentation for
-
# ActiveModel::Type::Value. For more details on registering your types
-
# to be referenced by a symbol, see ActiveRecord::Type.register. You can
-
# also pass a type object directly, in place of a symbol.
-
#
-
# ==== \Querying
-
#
-
# When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
-
# use the type defined by the model class to convert the value to SQL,
-
# calling +serialize+ on your type object. For example:
-
#
-
# class Money < Struct.new(:amount, :currency)
-
# end
-
#
-
# class MoneyType < Type::Value
-
# def initialize(currency_converter:)
-
# @currency_converter = currency_converter
-
# end
-
#
-
# # value will be the result of +deserialize+ or
-
# # +cast+. Assumed to be an instance of +Money+ in
-
# # this case.
-
# def serialize(value)
-
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
-
# value_in_bitcoins.amount
-
# end
-
# end
-
#
-
# # config/initializers/types.rb
-
# ActiveRecord::Type.register(:money, MoneyType)
-
#
-
# # app/models/product.rb
-
# class Product < ActiveRecord::Base
-
# currency_converter = ConversionRatesFromTheInternet.new
-
# attribute :price_in_bitcoins, :money, currency_converter: currency_converter
-
# end
-
#
-
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
-
# # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
-
#
-
# Product.where(price_in_bitcoins: Money.new(5, "GBP"))
-
# # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
-
#
-
# ==== Dirty Tracking
-
#
-
# The type of an attribute is given the opportunity to change how dirty
-
# tracking is performed. The methods +changed?+ and +changed_in_place?+
-
# will be called from ActiveModel::Dirty. See the documentation for those
-
# methods in ActiveModel::Type::Value for more details.
-
3
def attribute(name, cast_type = nil, **options, &block)
-
569
name = name.to_s
-
569
reload_schema_from_cache
-
-
569
self.attributes_to_define_after_schema_loads =
-
attributes_to_define_after_schema_loads.merge(
-
name => [cast_type || block, options]
-
)
-
end
-
-
# This is the low level API which sits beneath +attribute+. It only
-
# accepts type objects, and will do its work immediately instead of
-
# waiting for the schema to load. Automatic schema detection and
-
# ClassMethods#attribute both call this under the hood. While this method
-
# is provided so it can be used by plugin authors, application code
-
# should probably use ClassMethods#attribute.
-
#
-
# +name+ The name of the attribute being defined. Expected to be a +String+.
-
#
-
# +cast_type+ The type object to use for this attribute.
-
#
-
# +default+ The default value to use when no value is provided. If this option
-
# is not passed, the previous default value (if any) will be used.
-
# Otherwise, the default will be +nil+. A proc can also be passed, and
-
# will be called once each time a new value is needed.
-
#
-
# +user_provided_default+ Whether the default value should be cast using
-
# +cast+ or +deserialize+.
-
3
def define_attribute(
-
name,
-
cast_type,
-
default: NO_DEFAULT_PROVIDED,
-
user_provided_default: true
-
)
-
30036
attribute_types[name] = cast_type
-
30036
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
-
end
-
-
3
def load_schema! # :nodoc:
-
3471
super
-
3462
attributes_to_define_after_schema_loads.each do |name, (type, options)|
-
1458
define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default))
-
end
-
end
-
-
3
private
-
3
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
-
3
private_constant :NO_DEFAULT_PROVIDED
-
-
3
def define_default_attribute(name, value, type, from_user:)
-
30036
if value == NO_DEFAULT_PROVIDED
-
1324
default_attribute = _default_attributes[name].with_type(type)
-
28712
elsif from_user
-
126
default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
-
name,
-
value,
-
type,
-
39
_default_attributes.fetch(name.to_s) { nil },
-
)
-
else
-
28586
default_attribute = ActiveModel::Attribute.from_database(name, value, type)
-
end
-
30036
_default_attributes[name] = default_attribute
-
end
-
-
3
def decorate_attribute_type(attr_name, **default)
-
396
type, options = attributes_to_define_after_schema_loads[attr_name]
-
-
396
attribute(attr_name, **default) do |cast_type|
-
790
if type && !type.is_a?(Proc)
-
9
cast_type = _lookup_cast_type(attr_name, type, options)
-
end
-
-
790
yield cast_type
-
end
-
end
-
-
3
def _lookup_cast_type(name, type, options)
-
1467
case type
-
when Symbol
-
608
adapter_name = ActiveRecord::Type.adapter_name_from(self)
-
608
ActiveRecord::Type.lookup(type, **options.except(:default), adapter: adapter_name)
-
when Proc
-
790
type[type_for_attribute(name)]
-
else
-
69
type || type_for_attribute(name)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record Autosave Association
-
#
-
# AutosaveAssociation is a module that takes care of automatically saving
-
# associated records when their parent is saved. In addition to saving, it
-
# also destroys any associated records that were marked for destruction.
-
# (See #mark_for_destruction and #marked_for_destruction?).
-
#
-
# Saving of the parent, its associations, and the destruction of marked
-
# associations, all happen inside a transaction. This should never leave the
-
# database in an inconsistent state.
-
#
-
# If validations for any of the associations fail, their error messages will
-
# be applied to the parent.
-
#
-
# Note that it also means that associations marked for destruction won't
-
# be destroyed directly. They will however still be marked for destruction.
-
#
-
# Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
-
# When the <tt>:autosave</tt> option is not present then new association records are
-
# saved but the updated association records are not saved.
-
#
-
# == Validation
-
#
-
# Child records are validated unless <tt>:validate</tt> is +false+.
-
#
-
# == Callbacks
-
#
-
# Association with autosave option defines several callbacks on your
-
# model (around_save, before_save, after_create, after_update). Please note that
-
# callbacks are executed in the order they were defined in
-
# model. You should avoid modifying the association content before
-
# autosave callbacks are executed. Placing your callbacks after
-
# associations is usually a good practice.
-
#
-
# === One-to-one Example
-
#
-
# class Post < ActiveRecord::Base
-
# has_one :author, autosave: true
-
# end
-
#
-
# Saving changes to the parent and its associated model can now be performed
-
# automatically _and_ atomically:
-
#
-
# post = Post.find(1)
-
# post.title # => "The current global position of migrating ducks"
-
# post.author.name # => "alloy"
-
#
-
# post.title = "On the migration of ducks"
-
# post.author.name = "Eloy Duran"
-
#
-
# post.save
-
# post.reload
-
# post.title # => "On the migration of ducks"
-
# post.author.name # => "Eloy Duran"
-
#
-
# Destroying an associated model, as part of the parent's save action, is as
-
# simple as marking it for destruction:
-
#
-
# post.author.mark_for_destruction
-
# post.author.marked_for_destruction? # => true
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.author.id
-
# Author.find_by(id: id).nil? # => false
-
#
-
# post.save
-
# post.reload.author # => nil
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Author.find_by(id: id).nil? # => true
-
#
-
# === One-to-many Example
-
#
-
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments # :autosave option is not declared
-
# end
-
#
-
# post = Post.new(title: 'ruby rocks')
-
# post.comments.build(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# post.comments.build(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# comment = post.comments.create(body: 'hello world')
-
# comment.body = 'hi everyone'
-
# post.save # => saves post, but not comment
-
#
-
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
-
# are new records or not:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments, autosave: true
-
# end
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# comment = post.comments.create(body: 'hello world')
-
# comment.body = 'hi everyone'
-
# post.comments.build(body: "good morning.")
-
# post.save # => saves post and both comments.
-
#
-
# Destroying one of the associated models as part of the parent's save action
-
# is as simple as marking it for destruction:
-
#
-
# post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
-
# post.comments[1].mark_for_destruction
-
# post.comments[1].marked_for_destruction? # => true
-
# post.comments.length # => 2
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.comments.last.id
-
# Comment.find_by(id: id).nil? # => false
-
#
-
# post.save
-
# post.reload.comments.length # => 1
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Comment.find_by(id: id).nil? # => true
-
#
-
# === Caveats
-
#
-
# Note that autosave will only trigger for already-persisted association records
-
# if the records themselves have been changed. This is to protect against
-
# <tt>SystemStackError</tt> caused by circular association validations. The one
-
# exception is if a custom validation context is used, in which case the validations
-
# will always fire on the associated records.
-
3
module AutosaveAssociation
-
3
extend ActiveSupport::Concern
-
-
3
module AssociationBuilderExtension #:nodoc:
-
3
def self.build(model, reflection)
-
3590
model.send(:add_autosave_association_callbacks, reflection)
-
end
-
-
3
def self.valid_options
-
3602
[ :autosave ]
-
end
-
end
-
-
3
included do
-
3
Associations::Builder::Association.extensions << AssociationBuilderExtension
-
3
mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
-
end
-
-
3
module ClassMethods # :nodoc:
-
3
private
-
3
def define_non_cyclic_method(name, &block)
-
5086
return if instance_methods(false).include?(name)
-
5059
define_method(name) do |*args|
-
268793
result = true; @_already_called ||= {}
-
# Loop prevention for validation of associations
-
268793
unless @_already_called[name]
-
267955
begin
-
267955
@_already_called[name] = true
-
267955
result = instance_eval(&block)
-
ensure
-
267955
@_already_called[name] = false
-
end
-
end
-
-
268730
result
-
end
-
end
-
-
# Adds validation and save callbacks for the association as specified by
-
# the +reflection+.
-
#
-
# For performance reasons, we don't check whether to validate at runtime.
-
# However the validation and callback methods are lazy and those methods
-
# get created when they are invoked for the very first time. However,
-
# this can change, for instance, when using nested attributes, which is
-
# called _after_ the association has been defined. Since we don't want
-
# the callbacks to get defined multiple times, there are guards that
-
# check if the save or validation methods have already been defined
-
# before actually defining them.
-
3
def add_autosave_association_callbacks(reflection)
-
3602
save_method = :"autosave_associated_records_for_#{reflection.name}"
-
-
3602
if reflection.collection?
-
1898
around_save :around_save_collection_association
-
-
113971
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
-
# Doesn't use after_save as that would save associations added in after_create/after_update twice
-
1898
after_create save_method
-
1898
after_update save_method
-
1704
elsif reflection.has_one?
-
18633
define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
-
# Configures two callbacks instead of a single after_save so that
-
# the model may rely on their execution order relative to its
-
# own callbacks.
-
#
-
# For example, given that after_creates run before after_saves, if
-
# we configured instead an after_save there would be no way to fire
-
# a custom after_create callback after the child association gets
-
# created.
-
408
after_create save_method
-
408
after_update save_method
-
else
-
28215
define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
-
1296
before_save save_method
-
end
-
-
3602
define_autosave_validation_callbacks(reflection)
-
end
-
-
3
def define_autosave_validation_callbacks(reflection)
-
3920
validation_method = :"validate_associated_records_for_#{reflection.name}"
-
3920
if reflection.validate? && !method_defined?(validation_method)
-
1892
if reflection.collection?
-
1823
method = :validate_collection_association
-
else
-
69
method = :validate_single_association
-
end
-
-
130855
define_non_cyclic_method(validation_method) { send(method, reflection) }
-
1892
validate validation_method
-
1892
after_validation :_ensure_no_duplicate_errors
-
end
-
end
-
end
-
-
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
-
3
def reload(options = nil)
-
2233
@marked_for_destruction = false
-
2233
@destroyed_by_association = nil
-
2233
super
-
end
-
-
# Marks this record to be destroyed as part of the parent's save transaction.
-
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
-
# when <tt>parent.save</tt> is called.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
3
def mark_for_destruction
-
225
@marked_for_destruction = true
-
end
-
-
# Returns whether or not this record will be destroyed as part of the parent's save transaction.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
3
def marked_for_destruction?
-
9877
@marked_for_destruction
-
end
-
-
# Records the association that is being destroyed and destroying this
-
# record in the process.
-
3
def destroyed_by_association=(reflection)
-
225
@destroyed_by_association = reflection
-
end
-
-
# Returns the association for the parent being destroyed.
-
#
-
# Used to avoid updating the counter cache unnecessarily.
-
3
def destroyed_by_association
-
281
@destroyed_by_association
-
end
-
-
# Returns whether or not this record has been changed in any way (including whether
-
# any of its nested autosave associations are likewise changed)
-
3
def changed_for_autosave?
-
8098
new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
-
end
-
-
3
private
-
# Returns the record for an association collection that should be validated
-
# or saved. If +autosave+ is +false+ only new records will be returned,
-
# unless the parent is/was a new record itself.
-
3
def associated_records_to_validate_or_save(association, new_record, autosave)
-
3031
if new_record || custom_validation_context?
-
1358
association && association.target
-
1673
elsif autosave
-
948
association.target.find_all(&:changed_for_autosave?)
-
else
-
725
association.target.find_all(&:new_record?)
-
end
-
end
-
-
# Go through nested autosave associations that are loaded in memory (without loading
-
# any new ones), and return true if any are changed for autosave.
-
# Returns false if already called to prevent an infinite loop.
-
3
def nested_records_changed_for_autosave?
-
5542
@_nested_records_changed_for_autosave_already_called ||= false
-
5542
return false if @_nested_records_changed_for_autosave_already_called
-
4177
begin
-
4177
@_nested_records_changed_for_autosave_already_called = true
-
4177
self.class._reflections.values.any? do |reflection|
-
47377
if reflection.options[:autosave]
-
21468
association = association_instance_get(reflection.name)
-
21468
association && Array.wrap(association.target).any?(&:changed_for_autosave?)
-
end
-
end
-
ensure
-
4177
@_nested_records_changed_for_autosave_already_called = false
-
end
-
end
-
-
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
-
# turned on for the association.
-
3
def validate_single_association(reflection)
-
8316
association = association_instance_get(reflection.name)
-
8316
record = association && association.reader
-
8316
association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
-
end
-
-
# Validate the associated records if <tt>:validate</tt> or
-
# <tt>:autosave</tt> is turned on for the association specified by
-
# +reflection+.
-
3
def validate_collection_association(reflection)
-
120647
if association = association_instance_get(reflection.name)
-
1572
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
-
3068
records.each_with_index { |record, index| association_valid?(reflection, record, index) }
-
end
-
end
-
end
-
-
# Returns whether or not the association is valid and applies any errors to
-
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
-
# enabled records if they're marked_for_destruction? or destroyed.
-
3
def association_valid?(reflection, record, index = nil)
-
2402
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
-
-
2119
context = validation_context if custom_validation_context?
-
-
2119
unless valid = record.valid?(context)
-
301
if reflection.options[:autosave]
-
247
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
-
-
247
record.errors.group_by_attribute.each { |attribute, errors|
-
256
attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
-
-
256
errors.each { |error|
-
259
self.errors.import(
-
error,
-
attribute: attribute
-
)
-
}
-
}
-
else
-
54
errors.add(reflection.name)
-
end
-
end
-
2119
valid
-
end
-
-
3
def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
-
256
if indexed_attribute
-
12
"#{reflection.name}[#{index}].#{attribute}"
-
else
-
244
"#{reflection.name}.#{attribute}"
-
end
-
end
-
-
# Is used as an around_save callback to check while saving a collection
-
# association whether or not the parent was a new record before saving.
-
3
def around_save_collection_association
-
9857
previously_new_record_before_save = (@new_record_before_save ||= false)
-
9857
@new_record_before_save = !previously_new_record_before_save && new_record?
-
-
9857
yield
-
ensure
-
9857
@new_record_before_save = previously_new_record_before_save
-
end
-
-
# Saves any new associated records, or all loaded autosave associations if
-
# <tt>:autosave</tt> is enabled on the association.
-
#
-
# In addition, it destroys all children that were marked for destruction
-
# with #mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
3
def save_collection_association(reflection)
-
112073
if association = association_instance_get(reflection.name)
-
1459
autosave = reflection.options[:autosave]
-
-
# By saving the instance variable in a local variable,
-
# we make the whole callback re-entrant.
-
1459
new_record_before_save = @new_record_before_save
-
-
# reconstruct the scope now that we know the owner's id
-
1459
association.reset_scope
-
-
1459
if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
-
1459
if autosave
-
614
records_to_destroy = records.select(&:marked_for_destruction?)
-
713
records_to_destroy.each { |record| association.destroy(record) }
-
608
records -= records_to_destroy
-
end
-
-
1453
records.each do |record|
-
1198
next if record.destroyed?
-
-
1198
saved = true
-
-
1198
if autosave != false && (new_record_before_save || record.new_record?)
-
931
if autosave
-
299
saved = association.insert_record(record, false)
-
632
elsif !reflection.nested?
-
626
association_saved = association.insert_record(record)
-
-
626
if reflection.validate?
-
623
errors.add(reflection.name) unless association_saved
-
623
saved = association_saved
-
end
-
end
-
267
elsif autosave
-
264
saved = record.save(validate: false)
-
end
-
-
1189
raise(RecordInvalid.new(association.owner)) unless saved
-
end
-
end
-
end
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
-
# on the association.
-
#
-
# In addition, it will destroy the association if it was marked for
-
# destruction with #mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
3
def save_has_one_association(reflection)
-
18225
association = association_instance_get(reflection.name)
-
18225
record = association && association.load_target
-
-
18225
if record && !record.destroyed?
-
598
autosave = reflection.options[:autosave]
-
-
598
if autosave && record.marked_for_destruction?
-
27
record.destroy
-
571
elsif autosave != false
-
568
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
-
-
568
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
-
439
unless reflection.through_reflection
-
439
record[reflection.foreign_key] = key
-
439
if inverse_reflection = reflection.inverse_of
-
403
record.association(inverse_reflection.name).loaded!
-
end
-
end
-
-
439
saved = record.save(validate: !autosave)
-
424
raise ActiveRecord::Rollback if !saved && autosave
-
419
saved
-
end
-
end
-
end
-
end
-
-
# If the record is new or it has changed, returns true.
-
3
def record_changed?(reflection, record, key)
-
273
record.new_record? ||
-
association_foreign_key_changed?(reflection, record, key) ||
-
record.will_save_change_to_attribute?(reflection.foreign_key)
-
end
-
-
3
def association_foreign_key_changed?(reflection, record, key)
-
201
return false if reflection.through_reflection?
-
-
177
record._has_attribute?(reflection.foreign_key) && record._read_attribute(reflection.foreign_key) != key
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
-
#
-
# In addition, it will destroy the association if it was marked for destruction.
-
3
def save_belongs_to_association(reflection)
-
26919
association = association_instance_get(reflection.name)
-
26919
return unless association && association.loaded? && !association.stale_target?
-
-
4245
record = association.load_target
-
4245
if record && !record.destroyed?
-
3911
autosave = reflection.options[:autosave]
-
-
3911
if autosave && record.marked_for_destruction?
-
30
self[reflection.foreign_key] = nil
-
30
record.destroy
-
3881
elsif autosave != false
-
3878
saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
-
-
3863
if association.updated?
-
1933
association_id = record.send(reflection.options[:primary_key] || :id)
-
1933
self[reflection.foreign_key] = association_id
-
1933
association.loaded!
-
end
-
-
3863
saved if autosave
-
end
-
end
-
end
-
-
3
def custom_validation_context?
-
4553
validation_context && [:create, :update].exclude?(validation_context)
-
end
-
-
3
def _ensure_no_duplicate_errors
-
12137
errors.uniq!
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/benchmarkable"
-
3
require "active_support/dependencies"
-
3
require "active_support/descendants_tracker"
-
3
require "active_support/time"
-
3
require "active_support/core_ext/class/subclasses"
-
3
require "active_record/log_subscriber"
-
3
require "active_record/explain_subscriber"
-
3
require "active_record/relation/delegation"
-
3
require "active_record/attributes"
-
3
require "active_record/type_caster"
-
3
require "active_record/database_configurations"
-
-
3
module ActiveRecord #:nodoc:
-
# = Active Record
-
#
-
# Active Record objects don't specify their attributes directly, but rather infer them from
-
# the table definition with which they're linked. Adding, removing, and changing attributes
-
# and their type is done directly in the database. Any change is instantly reflected in the
-
# Active Record objects. The mapping that binds a given Active Record class to a certain
-
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
-
#
-
# See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
-
#
-
# == Creation
-
#
-
# Active Records accept constructor parameters either in a hash or as a block. The hash
-
# method is especially useful when you're receiving the data from somewhere else, like an
-
# HTTP request. It works like this:
-
#
-
# user = User.new(name: "David", occupation: "Code Artist")
-
# user.name # => "David"
-
#
-
# You can also use block initialization:
-
#
-
# user = User.new do |u|
-
# u.name = "David"
-
# u.occupation = "Code Artist"
-
# end
-
#
-
# And of course you can just create a bare object and specify the attributes after the fact:
-
#
-
# user = User.new
-
# user.name = "David"
-
# user.occupation = "Code Artist"
-
#
-
# == Conditions
-
#
-
# Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
-
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
-
# be used for statements that don't involve tainted data. The hash form works much like the array form, except
-
# only equality and range is possible. Examples:
-
#
-
# class User < ActiveRecord::Base
-
# def self.authenticate_unsafely(user_name, password)
-
# where("user_name = '#{user_name}' AND password = '#{password}'").first
-
# end
-
#
-
# def self.authenticate_safely(user_name, password)
-
# where("user_name = ? AND password = ?", user_name, password).first
-
# end
-
#
-
# def self.authenticate_safely_simply(user_name, password)
-
# where(user_name: user_name, password: password).first
-
# end
-
# end
-
#
-
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
-
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
-
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
-
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
-
# before inserting them in the query, which will ensure that an attacker can't escape the
-
# query and fake the login (or worse).
-
#
-
# When using multiple parameters in the conditions, it can easily become hard to read exactly
-
# what the fourth or fifth question mark is supposed to represent. In those cases, you can
-
# resort to named bind variables instead. That's done by replacing the question marks with
-
# symbols and supplying a hash with values for the matching symbol keys:
-
#
-
# Company.where(
-
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
-
# { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' }
-
# ).first
-
#
-
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
-
# operator. For instance:
-
#
-
# Student.where(first_name: "Harvey", status: 1)
-
# Student.where(params[:student])
-
#
-
# A range may be used in the hash to use the SQL BETWEEN operator:
-
#
-
# Student.where(grade: 9..12)
-
#
-
# An array may be used in the hash to use the SQL IN operator:
-
#
-
# Student.where(grade: [9,11,12])
-
#
-
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
-
# can be used to qualify the table name of a particular condition. For instance:
-
#
-
# Student.joins(:schools).where(schools: { category: 'public' })
-
# Student.joins(:schools).where('schools.category' => 'public' )
-
#
-
# == Overwriting default accessors
-
#
-
# All column values are automatically available through basic accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling
-
# +super+ to actually change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses an integer of seconds to hold the length of the song
-
#
-
# def length=(minutes)
-
# super(minutes.to_i * 60)
-
# end
-
#
-
# def length
-
# super / 60
-
# end
-
# end
-
#
-
# == Attribute query methods
-
#
-
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
-
# Query methods allow you to test whether an attribute value is present.
-
# Additionally, when dealing with numeric values, a query method will return false if the value is zero.
-
#
-
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
-
# to determine whether the user has a name:
-
#
-
# user = User.new(name: "David")
-
# user.name? # => true
-
#
-
# anonymous = User.new(name: "")
-
# anonymous.name? # => false
-
#
-
# == Accessing attributes before they have been typecasted
-
#
-
# Sometimes you want to be able to read the raw attribute data without having the column-determined
-
# typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
-
# accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
-
# you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
-
#
-
# This is especially useful in validation situations where the user might supply a string for an
-
# integer field and you want to display the original string back in an error message. Accessing the
-
# attribute normally would typecast the string to 0, which isn't what you want.
-
#
-
# == Dynamic attribute-based finders
-
#
-
# Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
-
# by simple queries without turning to SQL. They work by appending the name of an attribute
-
# to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
-
# Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
-
# <tt>Person.find_by_user_name(user_name)</tt>.
-
#
-
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
-
# ActiveRecord::RecordNotFound error if they do not return any records,
-
# like <tt>Person.find_by_last_name!</tt>.
-
#
-
# It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
-
# "_and_".
-
#
-
# Person.find_by(user_name: user_name, password: password)
-
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
-
#
-
# It's even possible to call these dynamic finder methods on relations and named scopes.
-
#
-
# Payment.order("created_on").find_by_amount(50)
-
#
-
# == Saving arrays, hashes, and other non-mappable objects in text columns
-
#
-
# Active Record can serialize any object in text columns using YAML. To do so, you must
-
# specify this with a call to the class method
-
# {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize].
-
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing
-
# any additional work.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
#
-
# user = User.create(preferences: { "background" => "black", "display" => large })
-
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
-
#
-
# You can also specify a class option as the second parameter that'll raise an exception
-
# if a serialized object is retrieved as a descendant of a class not in the hierarchy.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, Hash
-
# end
-
#
-
# user = User.create(preferences: %w( one two three ))
-
# User.find(user.id).preferences # raises SerializationTypeMismatch
-
#
-
# When you specify a class option, the default value for that attribute will be a new
-
# instance of that class.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, OpenStruct
-
# end
-
#
-
# user = User.new
-
# user.preferences.theme_color = "red"
-
#
-
#
-
# == Single table inheritance
-
#
-
# Active Record allows inheritance by storing the name of the class in a
-
# column that is named "type" by default. See ActiveRecord::Inheritance for
-
# more details.
-
#
-
# == Connection to multiple databases in different models
-
#
-
# Connections are usually created through
-
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
-
# by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
-
# connection. But you can also set a class-specific connection. For example, if Course is an
-
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
-
# and Course and all of its subclasses will use this connection instead.
-
#
-
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
-
# a hash indexed by the class. If a connection is requested, the
-
# {ActiveRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method
-
# will go up the class-hierarchy until a connection is found in the connection pool.
-
#
-
# == Exceptions
-
#
-
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
-
# * AdapterNotSpecified - The configuration hash used in
-
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
-
# didn't include an <tt>:adapter</tt> key.
-
# * AdapterNotFound - The <tt>:adapter</tt> key used in
-
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
-
# specified a non-existent adapter
-
# (or a bad spelling of an existing one).
-
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
-
# specified in the association definition.
-
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
-
# You can inspect the +attribute+ property of the exception object to determine which attribute
-
# triggered the error.
-
# * ConnectionNotEstablished - No connection has been established.
-
# Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
-
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
-
# The +errors+ property of this exception contains an array of
-
# AttributeAssignmentError
-
# objects that should be inspected to determine which attributes triggered the errors.
-
# * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
-
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
-
# when the record is invalid.
-
# * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method.
-
# Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
-
# Some {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal
-
# nothing was found, please check its documentation for further details.
-
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
-
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
-
#
-
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
-
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
-
# instances in the current object space.
-
3
class Base
-
3
extend ActiveModel::Naming
-
-
3
extend ActiveSupport::Benchmarkable
-
3
extend ActiveSupport::DescendantsTracker
-
-
3
extend ConnectionHandling
-
3
extend QueryCache::ClassMethods
-
3
extend Querying
-
3
extend Translation
-
3
extend DynamicMatchers
-
3
extend DelegatedType
-
3
extend Explain
-
3
extend Enum
-
3
extend Delegation::DelegateCache
-
3
extend Aggregations::ClassMethods
-
-
3
include Core
-
3
include Persistence
-
3
include ReadonlyAttributes
-
3
include ModelSchema
-
3
include Inheritance
-
3
include Scoping
-
3
include Sanitization
-
3
include AttributeAssignment
-
3
include ActiveModel::Conversion
-
3
include Integration
-
3
include Validations
-
3
include CounterCache
-
3
include Attributes
-
3
include Locking::Optimistic
-
3
include Locking::Pessimistic
-
3
include AttributeMethods
-
3
include Callbacks
-
3
include Timestamp
-
3
include Associations
-
3
include ActiveModel::SecurePassword
-
3
include AutosaveAssociation
-
3
include NestedAttributes
-
3
include Transactions
-
3
include TouchLater
-
3
include NoTouching
-
3
include Reflection
-
3
include Serialization
-
3
include Store
-
3
include SecureToken
-
3
include SignedId
-
3
include Suppressor
-
end
-
-
3
ActiveSupport.run_load_hooks(:active_record, Base)
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record \Callbacks
-
#
-
# \Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
-
# before or after a change in the object state. This can be used to make sure that associated and
-
# dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or
-
# to massage attributes before they're validated (by overwriting +before_validation+).
-
# As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record:
-
#
-
# * (-) <tt>save</tt>
-
# * (-) <tt>valid</tt>
-
# * (1) <tt>before_validation</tt>
-
# * (-) <tt>validate</tt>
-
# * (2) <tt>after_validation</tt>
-
# * (3) <tt>before_save</tt>
-
# * (4) <tt>before_create</tt>
-
# * (-) <tt>create</tt>
-
# * (5) <tt>after_create</tt>
-
# * (6) <tt>after_save</tt>
-
# * (7) <tt>after_commit</tt>
-
#
-
# Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
-
# Check out ActiveRecord::Transactions for more details about <tt>after_commit</tt> and
-
# <tt>after_rollback</tt>.
-
#
-
# Additionally, an <tt>after_touch</tt> callback is triggered whenever an
-
# object is touched.
-
#
-
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
-
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
-
# are instantiated as well.
-
#
-
# There are nineteen callbacks in total, which give a lot of control over how to react and prepare for each state in the
-
# Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar,
-
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
-
#
-
# Examples:
-
# class CreditCard < ActiveRecord::Base
-
# # Strip everything but digits, so the user can specify "555 234 34" or
-
# # "5552-3434" and both will mean "55523434"
-
# before_validation(on: :create) do
-
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
-
# end
-
# end
-
#
-
# class Subscription < ActiveRecord::Base
-
# before_create :record_signup
-
#
-
# private
-
# def record_signup
-
# self.signed_up_on = Date.today
-
# end
-
# end
-
#
-
# class Firm < ActiveRecord::Base
-
# # Disables access to the system, for associated clients and people when the firm is destroyed
-
# before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
-
# before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
-
# end
-
#
-
# == Inheritable callback queues
-
#
-
# Besides the overwritable callback methods, it's also possible to register callbacks through the
-
# use of the callback macros. Their main advantage is that the macros add behavior into a callback
-
# queue that is kept intact through an inheritance hierarchy.
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :destroy_author
-
# end
-
#
-
# class Reply < Topic
-
# before_destroy :destroy_readers
-
# end
-
#
-
# When <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
-
# run, both +destroy_author+ and +destroy_readers+ are called.
-
#
-
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
-
# callbacks before specifying the associations. Otherwise, you might trigger the loading of a
-
# child before the parent has registered the callbacks and they won't be inherited.
-
#
-
# == Types of callbacks
-
#
-
# There are three types of callbacks accepted by the callback macros: method references (symbol), callback objects,
-
# inline methods (using a proc). Method references and callback objects are the recommended approaches,
-
# inline methods using a proc are sometimes appropriate (such as for creating mix-ins).
-
#
-
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :delete_parents
-
#
-
# private
-
# def delete_parents
-
# self.class.delete_by(parent_id: id)
-
# end
-
# end
-
#
-
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new
-
# after_save EncryptionWrapper.new
-
# after_initialize EncryptionWrapper.new
-
# end
-
#
-
# class EncryptionWrapper
-
# def before_save(record)
-
# record.credit_card_number = encrypt(record.credit_card_number)
-
# end
-
#
-
# def after_save(record)
-
# record.credit_card_number = decrypt(record.credit_card_number)
-
# end
-
#
-
# alias_method :after_initialize, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# So you specify the object you want to be messaged on a given callback. When that callback is triggered, the object has
-
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
-
# initialization data such as the name of the attribute to work with:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new("credit_card_number")
-
# after_save EncryptionWrapper.new("credit_card_number")
-
# after_initialize EncryptionWrapper.new("credit_card_number")
-
# end
-
#
-
# class EncryptionWrapper
-
# def initialize(attribute)
-
# @attribute = attribute
-
# end
-
#
-
# def before_save(record)
-
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# def after_save(record)
-
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# alias_method :after_initialize, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# == <tt>before_validation*</tt> returning statements
-
#
-
# If the +before_validation+ callback throws +:abort+, the process will be
-
# aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
-
# If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
-
# Nothing will be appended to the errors object.
-
#
-
# == Canceling callbacks
-
#
-
# If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
-
# the associated action are cancelled.
-
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
-
# methods on the model, which are called last.
-
#
-
# == Ordering callbacks
-
#
-
# Sometimes application code requires that callbacks execute in a specific order. For example, a +before_destroy+
-
# callback (+log_children+ in this case) should be executed before records in the +children+ association are destroyed by the
-
# <tt>dependent: :destroy</tt> option.
-
#
-
# Let's look at the code below:
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: :destroy
-
#
-
# before_destroy :log_children
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# In this case, the problem is that when the +before_destroy+ callback is executed, records in the +children+ association no
-
# longer exist because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback was executed first.
-
# You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: :destroy
-
#
-
# before_destroy :log_children, prepend: true
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# This way, the +before_destroy+ is executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
-
#
-
# Also, there are cases when you want several callbacks of the same type to
-
# be executed in order.
-
#
-
# For example:
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children
-
#
-
# after_save :log_children
-
# after_save :do_something_else
-
#
-
# private
-
#
-
# def log_children
-
# # Child processing
-
# end
-
#
-
# def do_something_else
-
# # Something else
-
# end
-
# end
-
#
-
# In this case the +log_children+ is executed before +do_something_else+.
-
# The same applies to all non-transactional callbacks.
-
#
-
# As seen below, in case there are multiple transactional callbacks the order
-
# is reversed.
-
#
-
# For example:
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children
-
#
-
# after_commit :log_children
-
# after_commit :do_something_else
-
#
-
# private
-
#
-
# def log_children
-
# # Child processing
-
# end
-
#
-
# def do_something_else
-
# # Something else
-
# end
-
# end
-
#
-
# In this case the +do_something_else+ is executed before +log_children+.
-
#
-
# == \Transactions
-
#
-
# The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
-
# or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes <tt>after_*</tt> hooks.
-
# If everything goes fine a +COMMIT+ is executed once the chain has been completed.
-
#
-
# If a <tt>before_*</tt> callback cancels the action a +ROLLBACK+ is issued. You
-
# can also trigger a +ROLLBACK+ raising an exception in any of the callbacks,
-
# including <tt>after_*</tt> hooks. Note, however, that in that case the client
-
# needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception
-
# instead of quietly returning +false+.
-
#
-
# == Debugging callbacks
-
#
-
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. Active Model \Callbacks support
-
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
-
# defines what part of the chain the callback runs in.
-
#
-
# To find all callbacks in the +before_save+ callback chain:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
-
#
-
# Returns an array of callback objects that form the +before_save+ chain.
-
#
-
# To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
-
#
-
# Returns true or false depending on whether the proc is contained in the +before_save+ callback chain on a Topic model.
-
#
-
3
module Callbacks
-
3
extend ActiveSupport::Concern
-
-
3
CALLBACKS = [
-
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
-
:before_save, :around_save, :after_save, :before_create, :around_create,
-
:after_create, :before_update, :around_update, :after_update,
-
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
-
]
-
-
3
module ClassMethods # :nodoc:
-
3
include ActiveModel::Callbacks
-
end
-
-
3
included do
-
3
include ActiveModel::Validations::Callbacks
-
-
3
define_model_callbacks :initialize, :find, :touch, only: :after
-
3
define_model_callbacks :save, :create, :update, :destroy
-
end
-
-
3
def destroy #:nodoc:
-
1074
@_destroy_callback_already_called ||= false
-
1074
return if @_destroy_callback_already_called
-
1062
@_destroy_callback_already_called = true
-
2039
_run_destroy_callbacks { super }
-
rescue RecordNotDestroyed => e
-
6
@_association_destroy_exception = e
-
6
false
-
ensure
-
1074
@_destroy_callback_already_called = false
-
end
-
-
3
def touch(*, **) #:nodoc:
-
1074
_run_touch_callbacks { super }
-
end
-
-
3
def increment!(attribute, by = 1, touch: nil) # :nodoc:
-
528
touch ? _run_touch_callbacks { super } : super
-
end
-
-
3
private
-
3
def create_or_update(**)
-
31958
_run_save_callbacks { super }
-
end
-
-
3
def _create_record
-
24891
_run_create_callbacks { super }
-
end
-
-
3
def _update_record
-
6832
_run_update_callbacks { super }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Coders # :nodoc:
-
3
class JSON # :nodoc:
-
3
def self.dump(obj)
-
18
ActiveSupport::JSON.encode(obj)
-
end
-
-
3
def self.load(json)
-
96
ActiveSupport::JSON.decode(json) unless json.blank?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "yaml"
-
-
3
module ActiveRecord
-
3
module Coders # :nodoc:
-
3
class YAMLColumn # :nodoc:
-
3
attr_accessor :object_class
-
-
3
def initialize(attr_name, object_class = Object)
-
250
@attr_name = attr_name
-
250
@object_class = object_class
-
250
check_arity_of_constructor
-
end
-
-
3
def dump(obj)
-
3085
return if obj.nil?
-
-
3085
assert_valid_value(obj, action: "dump")
-
3082
YAML.dump obj
-
end
-
-
3
def load(yaml)
-
10030
return object_class.new if object_class != Object && yaml.nil?
-
9495
return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
-
1665
obj = YAML.load(yaml)
-
-
1659
assert_valid_value(obj, action: "load")
-
1650
obj ||= object_class.new if object_class != Object
-
-
1650
obj
-
end
-
-
3
def assert_valid_value(obj, action:)
-
5305
unless obj.nil? || obj.is_a?(object_class)
-
15
raise SerializationTypeMismatch,
-
"can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
-
end
-
end
-
-
3
private
-
3
def check_arity_of_constructor
-
250
load(nil)
-
rescue ArgumentError
-
3
raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
extend ActiveSupport::Autoload
-
-
3
eager_autoload do
-
3
autoload :AbstractAdapter
-
end
-
-
3
autoload :Column
-
3
autoload :PoolConfig
-
3
autoload :PoolManager
-
-
3
autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
-
3
autoload :IndexDefinition
-
3
autoload :ColumnDefinition
-
3
autoload :ChangeColumnDefinition
-
3
autoload :ForeignKeyDefinition
-
3
autoload :CheckConstraintDefinition
-
3
autoload :TableDefinition
-
3
autoload :Table
-
3
autoload :AlterTable
-
3
autoload :ReferenceDefinition
-
end
-
-
3
autoload_at "active_record/connection_adapters/abstract/connection_pool" do
-
3
autoload :ConnectionHandler
-
end
-
-
3
autoload_under "abstract" do
-
3
autoload :SchemaStatements
-
3
autoload :DatabaseStatements
-
3
autoload :DatabaseLimits
-
3
autoload :Quoting
-
3
autoload :ConnectionPool
-
3
autoload :QueryCache
-
3
autoload :Savepoints
-
end
-
-
3
autoload_at "active_record/connection_adapters/abstract/transaction" do
-
3
autoload :TransactionManager
-
3
autoload :NullTransaction
-
3
autoload :RealTransaction
-
3
autoload :SavepointTransaction
-
3
autoload :TransactionState
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "thread"
-
3
require "concurrent/map"
-
3
require "monitor"
-
3
require "weakref"
-
-
3
module ActiveRecord
-
# Raised when a connection could not be obtained within the connection
-
# acquisition timeout period: because max connections in pool
-
# are in use.
-
3
class ConnectionTimeoutError < ConnectionNotEstablished
-
end
-
-
# Raised when a pool was unable to get ahold of all its connections
-
# to perform a "group" action such as
-
# {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
-
# or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
-
3
class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
-
end
-
-
3
module ConnectionAdapters
-
3
module AbstractPool # :nodoc:
-
3
def get_schema_cache(connection)
-
183851
self.schema_cache ||= SchemaCache.new(connection)
-
183851
schema_cache.connection = connection
-
183851
schema_cache
-
end
-
-
3
def set_schema_cache(cache)
-
self.schema_cache = cache
-
end
-
end
-
-
3
class NullPool # :nodoc:
-
3
include ConnectionAdapters::AbstractPool
-
-
3
attr_accessor :schema_cache
-
end
-
-
# Connection pool base class for managing Active Record database
-
# connections.
-
#
-
# == Introduction
-
#
-
# A connection pool synchronizes thread access to a limited number of
-
# database connections. The basic idea is that each thread checks out a
-
# database connection from the pool, uses that connection, and checks the
-
# connection back in. ConnectionPool is completely thread-safe, and will
-
# ensure that a connection cannot be used by two threads at the same time,
-
# as long as ConnectionPool's contract is correctly followed. It will also
-
# handle cases in which there are more threads than connections: if all
-
# connections have been checked out, and a thread tries to checkout a
-
# connection anyway, then ConnectionPool will wait until some other thread
-
# has checked in a connection.
-
#
-
# == Obtaining (checking out) a connection
-
#
-
# Connections can be obtained and used from a connection pool in several
-
# ways:
-
#
-
# 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
-
# as with Active Record 2.1 and
-
# earlier (pre-connection-pooling). Eventually, when you're done with
-
# the connection(s) and wish it to be returned to the pool, you call
-
# {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
-
# This will be the default behavior for Active Record when used in conjunction with
-
# Action Pack's request handling cycle.
-
# 2. Manually check out a connection from the pool with
-
# {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
-
# returning this connection to the pool when finished by calling
-
# {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin].
-
# 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which
-
# obtains a connection, yields it as the sole argument to the block,
-
# and returns it to the pool after the block completes.
-
#
-
# Connections in the pool are actually AbstractAdapter objects (or objects
-
# compatible with AbstractAdapter's interface).
-
#
-
# == Options
-
#
-
# There are several connection-pooling-related options that you can add to
-
# your database connection configuration:
-
#
-
# * +pool+: maximum number of connections the pool may manage (default 5).
-
# * +idle_timeout+: number of seconds that a connection will be kept
-
# unused in the pool before it is automatically disconnected (default
-
# 300 seconds). Set this to zero to keep connections forever.
-
# * +checkout_timeout+: number of seconds to wait for a connection to
-
# become available before giving up and raising a timeout error (default
-
# 5 seconds).
-
#
-
#--
-
# Synchronization policy:
-
# * all public methods can be called outside +synchronize+
-
# * access to these instance variables needs to be in +synchronize+:
-
# * @connections
-
# * @now_connecting
-
# * private methods that require being called in a +synchronize+ blocks
-
# are now explicitly documented
-
3
class ConnectionPool
-
# Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
-
# with which it shares a Monitor.
-
3
class Queue
-
3
def initialize(lock = Monitor.new)
-
975
@lock = lock
-
975
@cond = @lock.new_cond
-
975
@num_waiting = 0
-
975
@queue = []
-
end
-
-
# Test if any threads are currently waiting on the queue.
-
3
def any_waiting?
-
14
synchronize do
-
14
@num_waiting > 0
-
end
-
end
-
-
# Returns the number of threads currently waiting on this
-
# queue.
-
3
def num_waiting
-
866
synchronize do
-
866
@num_waiting
-
end
-
end
-
-
# Add +element+ to the queue. Never blocks.
-
3
def add(element)
-
112930
synchronize do
-
112930
@queue.push element
-
112930
@cond.signal
-
end
-
end
-
-
# If +element+ is in the queue, remove and return it, or +nil+.
-
3
def delete(element)
-
29
synchronize do
-
29
@queue.delete(element)
-
end
-
end
-
-
# Remove all elements from the queue.
-
3
def clear
-
1118
synchronize do
-
1118
@queue.clear
-
end
-
end
-
-
# Remove the head of the queue.
-
#
-
# If +timeout+ is not given, remove and return the head of the
-
# queue if the number of available elements is strictly
-
# greater than the number of threads currently waiting (that
-
# is, don't jump ahead in line). Otherwise, return +nil+.
-
#
-
# If +timeout+ is given, block if there is no element
-
# available, waiting up to +timeout+ seconds for an element to
-
# become available.
-
#
-
# Raises:
-
# - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
-
# becomes available within +timeout+ seconds,
-
3
def poll(timeout = nil)
-
226256
synchronize { internal_poll(timeout) }
-
end
-
-
3
private
-
3
def internal_poll(timeout)
-
113128
no_wait_poll || (timeout && wait_poll(timeout))
-
end
-
-
3
def synchronize(&block)
-
229209
@lock.synchronize(&block)
-
end
-
-
# Test if the queue currently contains any elements.
-
3
def any?
-
129
!@queue.empty?
-
end
-
-
# A thread can remove an element from the queue without
-
# waiting if and only if the number of currently available
-
# connections is strictly greater than the number of waiting
-
# threads.
-
3
def can_remove_no_wait?
-
113128
@queue.size > @num_waiting
-
end
-
-
# Removes and returns the head of the queue if possible, or +nil+.
-
3
def remove
-
112103
@queue.pop
-
end
-
-
# Remove and return the head of the queue if the number of
-
# available elements is strictly greater than the number of
-
# threads currently waiting. Otherwise, return +nil+.
-
3
def no_wait_poll
-
113128
remove if can_remove_no_wait?
-
end
-
-
# Waits on the queue up to +timeout+ seconds, then removes and
-
# returns the head of the queue.
-
3
def wait_poll(timeout)
-
123
@num_waiting += 1
-
-
123
t0 = Concurrent.monotonic_time
-
123
elapsed = 0
-
123
loop do
-
129
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
129
@cond.wait(timeout - elapsed)
-
end
-
-
129
return remove if any?
-
-
21
elapsed = Concurrent.monotonic_time - t0
-
21
if elapsed >= timeout
-
15
msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
-
[timeout, elapsed]
-
15
raise ConnectionTimeoutError, msg
-
end
-
end
-
ensure
-
123
@num_waiting -= 1
-
end
-
end
-
-
# Adds the ability to turn a basic fair FIFO queue into one
-
# biased to some thread.
-
3
module BiasableQueue # :nodoc:
-
3
class BiasedConditionVariable # :nodoc:
-
# semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
-
# +signal+ and +wait+ methods are only called while holding a lock
-
3
def initialize(lock, other_cond, preferred_thread)
-
562
@real_cond = lock.new_cond
-
562
@other_cond = other_cond
-
562
@preferred_thread = preferred_thread
-
562
@num_waiting_on_real_cond = 0
-
end
-
-
3
def broadcast
-
broadcast_on_biased
-
@other_cond.broadcast
-
end
-
-
3
def broadcast_on_biased
-
562
@num_waiting_on_real_cond = 0
-
562
@real_cond.broadcast
-
end
-
-
3
def signal
-
121
if @num_waiting_on_real_cond > 0
-
24
@num_waiting_on_real_cond -= 1
-
24
@real_cond
-
else
-
97
@other_cond
-
121
end.signal
-
end
-
-
3
def wait(timeout)
-
36
if Thread.current == @preferred_thread
-
36
@num_waiting_on_real_cond += 1
-
36
@real_cond
-
else
-
@other_cond
-
36
end.wait(timeout)
-
end
-
end
-
-
3
def with_a_bias_for(thread)
-
562
previous_cond = nil
-
562
new_cond = nil
-
562
synchronize do
-
562
previous_cond = @cond
-
562
@cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
-
end
-
562
yield
-
ensure
-
562
synchronize do
-
562
@cond = previous_cond if previous_cond
-
562
new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
-
end
-
end
-
end
-
-
# Connections must be leased while holding the main pool mutex. This is
-
# an internal subclass that also +.leases+ returned connections while
-
# still in queue's critical section (queue synchronizes with the same
-
# <tt>@lock</tt> as the main pool) so that a returned connection is already
-
# leased and there is no need to re-enter synchronized block.
-
3
class ConnectionLeasingQueue < Queue # :nodoc:
-
3
include BiasableQueue
-
-
3
private
-
3
def internal_poll(timeout)
-
113128
conn = super
-
113113
conn.lease if conn
-
113113
conn
-
end
-
end
-
-
# Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
-
# +pool+. A reaper instantiated with a zero frequency will never reap
-
# the connection pool.
-
#
-
# Configure the frequency by setting +reaping_frequency+ in your database
-
# yaml file (default 60 seconds).
-
3
class Reaper
-
3
attr_reader :pool, :frequency
-
-
3
def initialize(pool, frequency)
-
987
@pool = pool
-
987
@frequency = frequency
-
end
-
-
3
@mutex = Mutex.new
-
3
@pools = {}
-
3
@threads = {}
-
-
3
class << self
-
3
def register_pool(pool, frequency) # :nodoc:
-
984
@mutex.synchronize do
-
984
unless @threads[frequency]&.alive?
-
19
@threads[frequency] = spawn_thread(frequency)
-
end
-
984
@pools[frequency] ||= []
-
984
@pools[frequency] << WeakRef.new(pool)
-
end
-
end
-
-
3
private
-
3
def spawn_thread(frequency)
-
19
Thread.new(frequency) do |t|
-
19
running = true
-
19
while running
-
1521
sleep t
-
1518
@mutex.synchronize do
-
1518
@pools[frequency].select! do |pool|
-
2328
pool.weakref_alive? && !pool.discarded?
-
end
-
-
1518
@pools[frequency].each do |p|
-
1608
p.reap
-
1608
p.flush
-
rescue WeakRef::RefError
-
end
-
-
1518
if @pools[frequency].empty?
-
16
@pools.delete(frequency)
-
16
@threads.delete(frequency)
-
16
running = false
-
end
-
end
-
end
-
end
-
end
-
end
-
-
3
def run
-
987
return unless frequency && frequency > 0
-
984
self.class.register_pool(pool, frequency)
-
end
-
end
-
-
3
include MonitorMixin
-
3
include QueryCache::ConnectionPoolConfiguration
-
3
include ConnectionAdapters::AbstractPool
-
-
3
attr_accessor :automatic_reconnect, :checkout_timeout
-
3
attr_reader :db_config, :size, :reaper, :pool_config
-
-
3
delegate :schema_cache, :schema_cache=, to: :pool_config
-
-
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
-
# object which describes database connection information (e.g. adapter,
-
# host name, username, password, etc), as well as the maximum size for
-
# this ConnectionPool.
-
#
-
# The default ConnectionPool maximum size is 5.
-
3
def initialize(pool_config)
-
975
super()
-
-
975
@pool_config = pool_config
-
975
@db_config = pool_config.db_config
-
-
975
@checkout_timeout = db_config.checkout_timeout
-
975
@idle_timeout = db_config.idle_timeout
-
975
@size = db_config.pool
-
-
# This variable tracks the cache of threads mapped to reserved connections, with the
-
# sole purpose of speeding up the +connection+ method. It is not the authoritative
-
# registry of which thread owns which connection. Connection ownership is tracked by
-
# the +connection.owner+ attr on each +connection+ instance.
-
# The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
-
# then that +thread+ does indeed own that +conn+. However, an absence of a such
-
# mapping does not mean that the +thread+ doesn't own the said connection. In
-
# that case +conn.owner+ attr should be consulted.
-
# Access and modification of <tt>@thread_cached_conns</tt> does not require
-
# synchronization.
-
975
@thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
-
-
975
@connections = []
-
975
@automatic_reconnect = true
-
-
# Connection pool allows for concurrent (outside the main +synchronize+ section)
-
# establishment of new connections. This variable tracks the number of threads
-
# currently in the process of independently establishing connections to the DB.
-
975
@now_connecting = 0
-
-
975
@threads_blocking_new_connections = 0
-
-
975
@available = ConnectionLeasingQueue.new self
-
-
975
@lock_thread = false
-
-
975
@reaper = Reaper.new(self, db_config.reaping_frequency)
-
975
@reaper.run
-
end
-
-
3
def lock_thread=(lock_thread)
-
218634
if lock_thread
-
109317
@lock_thread = Thread.current
-
else
-
109317
@lock_thread = nil
-
end
-
end
-
-
# Retrieve the connection associated with the current thread, or call
-
# #checkout to obtain one if necessary.
-
#
-
# #connection can be called any number of times; the connection is
-
# held in a cache keyed by a thread.
-
3
def connection
-
1328083
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
-
end
-
-
# Returns true if there is an open connection being used for the current thread.
-
#
-
# This method only works for connections that have been obtained through
-
# #connection or #with_connection methods. Connections obtained through
-
# #checkout will not be detected by #active_connection?
-
3
def active_connection?
-
974923
@thread_cached_conns[connection_cache_key(current_thread)]
-
end
-
-
# Signal that the thread is finished with the current connection.
-
# #release_connection releases the connection-thread association
-
# and returns the connection to the pool.
-
#
-
# This method only works for connections that have been obtained through
-
# #connection or #with_connection methods, connections obtained through
-
# #checkout will not be automatically released.
-
3
def release_connection(owner_thread = Thread.current)
-
126177
if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
-
111989
checkin conn
-
end
-
end
-
-
# If a connection obtained through #connection or #with_connection methods
-
# already exists yield it to the block. If no such connection
-
# exists checkout a connection, yield it to the block, and checkin the
-
# connection when finished.
-
3
def with_connection
-
927
unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
-
211
conn = connection
-
208
fresh_connection = true
-
end
-
924
yield conn
-
ensure
-
927
release_connection if fresh_connection
-
end
-
-
# Returns true if a connection has already been opened.
-
3
def connected?
-
3182
synchronize { @connections.any? }
-
end
-
-
# Returns an array containing the connections currently in the pool.
-
# Access to the array does not require synchronization on the pool because
-
# the array is newly created and not retained by the pool.
-
#
-
# However; this method bypasses the ConnectionPool's thread-safe connection
-
# access pattern. A returned connection may be owned by another thread,
-
# unowned, or by happen-stance owned by the calling thread.
-
#
-
# Calling methods on a connection without ownership is subject to the
-
# thread-safety guarantees of the underlying method. Many of the methods
-
# on connection adapter classes are inherently multi-thread unsafe.
-
3
def connections
-
164
synchronize { @connections.dup }
-
end
-
-
# Disconnects all connections in the pool, and clears the pool.
-
#
-
# Raises:
-
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
-
# connections in the pool within a timeout interval (default duration is
-
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
-
3
def disconnect(raise_on_acquisition_timeout = true)
-
525
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
-
522
synchronize do
-
522
@connections.each do |conn|
-
566
if conn.in_use?
-
566
conn.steal!
-
566
checkin conn
-
end
-
566
conn.disconnect!
-
end
-
522
@connections = []
-
522
@available.clear
-
end
-
end
-
end
-
-
# Disconnects all connections in the pool, and clears the pool.
-
#
-
# The pool first tries to gain ownership of all connections. If unable to
-
# do so within a timeout interval (default duration is
-
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
-
# disconnected without any regard for other connection owning threads.
-
3
def disconnect!
-
516
disconnect(false)
-
end
-
-
# Discards all connections in the pool (even if they're currently
-
# leased!), along with the pool itself. Any further interaction with the
-
# pool (except #spec and #schema_cache) is undefined.
-
#
-
# See AbstractAdapter#discard!
-
3
def discard! # :nodoc:
-
109
synchronize do
-
109
return if self.discarded?
-
109
@connections.each do |conn|
-
36
conn.discard!
-
end
-
109
@connections = @available = @thread_cached_conns = nil
-
end
-
end
-
-
3
def discarded? # :nodoc:
-
5134
@connections.nil?
-
end
-
-
# Clears the cache which maps classes and re-connects connections that
-
# require reloading.
-
#
-
# Raises:
-
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
-
# connections in the pool within a timeout interval (default duration is
-
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
-
3
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
-
37
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
-
34
synchronize do
-
34
@connections.each do |conn|
-
38
if conn.in_use?
-
38
conn.steal!
-
38
checkin conn
-
end
-
38
conn.disconnect! if conn.requires_reloading?
-
end
-
34
@connections.delete_if(&:requires_reloading?)
-
34
@available.clear
-
end
-
end
-
end
-
-
# Clears the cache which maps classes and re-connects connections that
-
# require reloading.
-
#
-
# The pool first tries to gain ownership of all connections. If unable to
-
# do so within a timeout interval (default duration is
-
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
-
# clears the cache and reloads connections without any regard for other
-
# connection owning threads.
-
3
def clear_reloadable_connections!
-
25
clear_reloadable_connections(false)
-
end
-
-
# Check-out a database connection from the pool, indicating that you want
-
# to use it. You should call #checkin when you no longer need this.
-
#
-
# This is done by either returning and leasing existing connection, or by
-
# creating a new connection and leasing it.
-
#
-
# If all connections are leased and the pool is at capacity (meaning the
-
# number of currently leased connections is greater than or equal to the
-
# size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
-
#
-
# Returns: an AbstractAdapter object.
-
#
-
# Raises:
-
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
-
3
def checkout(checkout_timeout = @checkout_timeout)
-
112974
checkout_and_verify(acquire_connection(checkout_timeout))
-
end
-
-
# Check-in a database connection back into the pool, indicating that you
-
# no longer need this connection.
-
#
-
# +conn+: an AbstractAdapter object, which was obtained by earlier by
-
# calling #checkout on this pool.
-
3
def checkin(conn)
-
112912
conn.lock.synchronize do
-
112912
synchronize do
-
112912
remove_connection_from_thread_cache conn
-
-
112912
conn._run_checkin_callbacks do
-
112912
conn.expire
-
end
-
-
112912
@available.add conn
-
end
-
end
-
end
-
-
# Remove a connection from the connection pool. The connection will
-
# remain open and active but will no longer be managed by this pool.
-
3
def remove(conn)
-
14
needs_new_connection = false
-
-
14
synchronize do
-
14
remove_connection_from_thread_cache conn
-
-
14
@connections.delete conn
-
14
@available.delete conn
-
-
# @available.any_waiting? => true means that prior to removing this
-
# conn, the pool was at its max size (@connections.size == @size).
-
# This would mean that any threads stuck waiting in the queue wouldn't
-
# know they could checkout_new_connection, so let's do it for them.
-
# Because condition-wait loop is encapsulated in the Queue class
-
# (that in turn is oblivious to ConnectionPool implementation), threads
-
# that are "stuck" there are helpless. They have no way of creating
-
# new connections and are completely reliant on us feeding available
-
# connections into the Queue.
-
14
needs_new_connection = @available.any_waiting?
-
end
-
-
# This is intentionally done outside of the synchronized section as we
-
# would like not to hold the main mutex while checking out new connections.
-
# Thus there is some chance that needs_new_connection information is now
-
# stale, we can live with that (bulk_make_new_connections will make
-
# sure not to exceed the pool's @size limit).
-
14
bulk_make_new_connections(1) if needs_new_connection
-
end
-
-
# Recover lost connections for the pool. A lost connection can occur if
-
# a programmer forgets to checkin a connection at the end of a thread
-
# or a thread dies unexpectedly.
-
3
def reap
-
1768
stale_connections = synchronize do
-
1768
return if self.discarded?
-
@connections.select do |conn|
-
1944
conn.in_use? && !conn.owner.alive?
-
1763
end.each do |conn|
-
122
conn.steal!
-
end
-
end
-
-
1763
stale_connections.each do |conn|
-
122
if conn.active?
-
122
conn.reset!
-
122
checkin conn
-
else
-
remove conn
-
end
-
end
-
end
-
-
# Disconnect all connections that have been idle for at least
-
# +minimum_idle+ seconds. Connections currently checked out, or that were
-
# checked in less than +minimum_idle+ seconds ago, are unaffected.
-
3
def flush(minimum_idle = @idle_timeout)
-
1620
return if minimum_idle.nil?
-
-
1616
idle_connections = synchronize do
-
1616
return if self.discarded?
-
@connections.select do |conn|
-
1105
!conn.in_use? && conn.seconds_idle >= minimum_idle
-
1611
end.each do |conn|
-
15
conn.lease
-
-
15
@available.delete conn
-
15
@connections.delete conn
-
end
-
end
-
-
1611
idle_connections.each do |conn|
-
15
conn.disconnect!
-
end
-
end
-
-
# Disconnect all currently idle connections. Connections currently checked
-
# out are unaffected.
-
3
def flush!
-
3
reap
-
3
flush(-1)
-
end
-
-
3
def num_waiting_in_queue # :nodoc:
-
866
@available.num_waiting
-
end
-
-
# Return connection pool's usage statistic
-
# Example:
-
#
-
# ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
-
3
def stat
-
9
synchronize do
-
9
{
-
size: size,
-
connections: @connections.size,
-
9
busy: @connections.count { |c| c.in_use? && c.owner.alive? },
-
9
dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
-
9
idle: @connections.count { |c| !c.in_use? },
-
waiting: num_waiting_in_queue,
-
checkout_timeout: checkout_timeout
-
}
-
end
-
end
-
-
3
private
-
#--
-
# this is unfortunately not concurrent
-
3
def bulk_make_new_connections(num_new_conns_needed)
-
16
num_new_conns_needed.times do
-
# try_to_checkout_new_connection will not exceed pool's @size limit
-
16
if new_conn = try_to_checkout_new_connection
-
# make the new_conn available to the starving threads stuck @available Queue
-
16
checkin(new_conn)
-
end
-
end
-
end
-
-
#--
-
# From the discussion on GitHub:
-
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
-
# This hook-in method allows for easier monkey-patching fixes needed by
-
# JRuby users that use Fibers.
-
3
def connection_cache_key(thread)
-
2657895
thread
-
end
-
-
3
def current_thread
-
2417734
@lock_thread || Thread.current
-
end
-
-
# Take control of all existing connections so a "group" action such as
-
# reload/disconnect can be performed safely. It is no longer enough to
-
# wrap it in +synchronize+ because some pool's actions are allowed
-
# to be performed outside of the main +synchronize+ block.
-
3
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
-
562
with_new_connections_blocked do
-
562
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
-
556
yield
-
end
-
end
-
-
3
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
-
562
collected_conns = synchronize do
-
# account for our own connections
-
1172
@connections.select { |conn| conn.owner == Thread.current }
-
end
-
-
562
newly_checked_out = []
-
562
timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
-
-
562
@available.with_a_bias_for(Thread.current) do
-
562
loop do
-
851
synchronize do
-
851
return if collected_conns.size == @connections.size && @now_connecting == 0
-
301
remaining_timeout = timeout_time - Concurrent.monotonic_time
-
301
remaining_timeout = 0 if remaining_timeout < 0
-
301
conn = checkout_for_exclusive_access(remaining_timeout)
-
289
collected_conns << conn
-
289
newly_checked_out << conn
-
end
-
end
-
end
-
rescue ExclusiveConnectionTimeoutError
-
# <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
-
# timeouts and are expected to just give up: we've obtained as many connections
-
# as possible, note that in a case like that we don't return any of the
-
# +newly_checked_out+ connections.
-
-
12
if raise_on_acquisition_timeout
-
6
release_newly_checked_out = true
-
6
raise
-
end
-
rescue Exception # if something else went wrong
-
# this can't be a "naked" rescue, because we have should return conns
-
# even for non-StandardErrors
-
release_newly_checked_out = true
-
raise
-
ensure
-
562
if release_newly_checked_out && newly_checked_out
-
# releasing only those conns that were checked out in this method, conns
-
# checked outside this method (before it was called) are not for us to release
-
6
newly_checked_out.each { |conn| checkin(conn) }
-
end
-
end
-
-
#--
-
# Must be called in a synchronize block.
-
3
def checkout_for_exclusive_access(checkout_timeout)
-
301
checkout(checkout_timeout)
-
rescue ConnectionTimeoutError
-
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
-
# rescue block, because doing so would put it outside of synchronize section, without
-
# being in a critical section thread_report might become inaccurate
-
12
msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
-
-
12
thread_report = []
-
12
@connections.each do |conn|
-
12
unless conn.owner == Thread.current
-
12
thread_report << "#{conn} is owned by #{conn.owner}"
-
end
-
end
-
-
12
msg << " (#{thread_report.join(', ')})" if thread_report.any?
-
-
12
raise ExclusiveConnectionTimeoutError, msg
-
end
-
-
3
def with_new_connections_blocked
-
562
synchronize do
-
562
@threads_blocking_new_connections += 1
-
end
-
-
562
yield
-
ensure
-
562
num_new_conns_required = 0
-
-
562
synchronize do
-
562
@threads_blocking_new_connections -= 1
-
-
562
if @threads_blocking_new_connections.zero?
-
562
@available.clear
-
-
562
num_new_conns_required = num_waiting_in_queue
-
-
562
@connections.each do |conn|
-
21
next if conn.in_use?
-
-
15
@available.add conn
-
15
num_new_conns_required -= 1
-
end
-
end
-
end
-
-
562
bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
-
end
-
-
# Acquire a connection by one of 1) immediately removing one
-
# from the queue of available connections, 2) creating a new
-
# connection if the pool is not at capacity, 3) waiting on the
-
# queue for a connection to become available.
-
#
-
# Raises:
-
# - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
-
#
-
#--
-
# Implementation detail: the connection returned by +acquire_connection+
-
# will already be "+connection.lease+ -ed" to the current thread.
-
3
def acquire_connection(checkout_timeout)
-
# NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to
-
# +conn.lease+ the returned connection (and to do this in a +synchronized+
-
# section). This is not the cleanest implementation, as ideally we would
-
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
-
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
-
# of the said methods and avoid an additional +synchronize+ overhead.
-
112974
if conn = @available.poll || try_to_checkout_new_connection
-
112814
conn
-
else
-
154
reap
-
154
@available.poll(checkout_timeout)
-
end
-
end
-
-
#--
-
# if owner_thread param is omitted, this must be called in synchronize block
-
3
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
-
113057
@thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
-
end
-
3
alias_method :release, :remove_connection_from_thread_cache
-
-
3
def new_connection
-
866
Base.send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
-
866
conn.check_version
-
end
-
end
-
-
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
-
# to the DB is done outside main synchronized section.
-
#--
-
# Implementation constraint: a newly established connection returned by this
-
# method must be in the +.leased+ state.
-
3
def try_to_checkout_new_connection
-
# first in synchronized section check if establishing new conns is allowed
-
# and increment @now_connecting, to prevent overstepping this pool's @size
-
# constraint
-
1026
do_checkout = synchronize do
-
1026
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
-
872
@now_connecting += 1
-
end
-
end
-
1026
if do_checkout
-
872
begin
-
# if successfully incremented @now_connecting establish new connection
-
# outside of synchronized section
-
872
conn = checkout_new_connection
-
ensure
-
872
synchronize do
-
872
if conn
-
866
adopt_connection(conn)
-
# returned conn needs to be already leased
-
866
conn.lease
-
end
-
872
@now_connecting -= 1
-
end
-
end
-
end
-
end
-
-
3
def adopt_connection(conn)
-
869
conn.pool = self
-
869
@connections << conn
-
end
-
-
3
def checkout_new_connection
-
872
raise ConnectionNotEstablished unless @automatic_reconnect
-
866
new_connection
-
end
-
-
3
def checkout_and_verify(c)
-
112953
c._run_checkout_callbacks do
-
112953
c.verify!
-
end
-
112953
c
-
rescue
-
remove c
-
c.disconnect!
-
raise
-
end
-
end
-
-
# ConnectionHandler is a collection of ConnectionPool objects. It is used
-
# for keeping separate connection pools that connect to different databases.
-
#
-
# For example, suppose that you have 5 models, with the following hierarchy:
-
#
-
# class Author < ActiveRecord::Base
-
# end
-
#
-
# class BankAccount < ActiveRecord::Base
-
# end
-
#
-
# class Book < ActiveRecord::Base
-
# establish_connection :library_db
-
# end
-
#
-
# class ScaryBook < Book
-
# end
-
#
-
# class GoodBook < Book
-
# end
-
#
-
# And a database.yml that looked like this:
-
#
-
# development:
-
# database: my_application
-
# host: localhost
-
#
-
# library_db:
-
# database: library
-
# host: some.library.org
-
#
-
# Your primary database in the development environment is "my_application"
-
# but the Book model connects to a separate database called "library_db"
-
# (this can even be a database on a different machine).
-
#
-
# Book, ScaryBook and GoodBook will all use the same connection pool to
-
# "library_db" while Author, BankAccount, and any other models you create
-
# will use the default connection pool to "my_application".
-
#
-
# The various connection pools are managed by a single instance of
-
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
-
# All Active Record models use this handler to determine the connection pool that they
-
# should use.
-
#
-
# The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
-
# about the model. The model needs to pass a connection specification name to the handler,
-
# in order to look up the correct connection pool.
-
3
class ConnectionHandler
-
431
FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
-
3
private_constant :FINALIZER
-
-
3
def initialize
-
# These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
-
444
@owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
-
-
# Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
-
444
ObjectSpace.define_finalizer self, FINALIZER
-
end
-
-
3
def prevent_writes # :nodoc:
-
291250
Thread.current[:prevent_writes]
-
end
-
-
3
def prevent_writes=(prevent_writes) # :nodoc:
-
442
Thread.current[:prevent_writes] = prevent_writes
-
end
-
-
# Prevent writing to the database regardless of role.
-
#
-
# In some cases you may want to prevent writes to the database
-
# even if you are on a database that can write. `while_preventing_writes`
-
# will prevent writes to the database for the duration of the block.
-
3
def while_preventing_writes(enabled = true)
-
221
original, self.prevent_writes = self.prevent_writes, enabled
-
221
yield
-
ensure
-
221
self.prevent_writes = original
-
end
-
-
3
def connection_pool_names # :nodoc:
-
6
owner_to_pool_manager.keys
-
end
-
-
3
def connection_pool_list
-
1356382
owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
-
end
-
3
alias :connection_pools :connection_pool_list
-
-
3
def establish_connection(config, owner_name: Base.name, shard: Base.default_shard)
-
765
owner_name = config.to_s if config.is_a?(Symbol)
-
-
765
pool_config = resolve_pool_config(config, owner_name)
-
756
db_config = pool_config.db_config
-
-
# Protects the connection named `ActiveRecord::Base` from being removed
-
# if the user calls `establish_connection :primary`.
-
756
if owner_to_pool_manager.key?(pool_config.connection_specification_name)
-
292
remove_connection_pool(pool_config.connection_specification_name, shard: shard)
-
end
-
-
756
message_bus = ActiveSupport::Notifications.instrumenter
-
756
payload = {}
-
756
if pool_config
-
756
payload[:spec_name] = pool_config.connection_specification_name
-
756
payload[:config] = db_config.configuration_hash
-
end
-
-
756
owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
-
756
pool_manager = get_pool_manager(pool_config.connection_specification_name)
-
756
pool_manager.set_pool_config(shard, pool_config)
-
-
756
message_bus.instrument("!connection.active_record", payload) do
-
756
pool_config.pool
-
end
-
end
-
-
# Returns true if there are any active connections among the connection
-
# pools that the ConnectionHandler is managing.
-
3
def active_connections?
-
81
connection_pool_list.any?(&:active_connection?)
-
end
-
-
# Returns any connections in use by the current thread back to the pool,
-
# and also returns connections to the pool cached by threads that are no
-
# longer alive.
-
3
def clear_active_connections!
-
19061
connection_pool_list.each(&:release_connection)
-
end
-
-
# Clears the cache which maps classes.
-
#
-
# See ConnectionPool#clear_reloadable_connections! for details.
-
3
def clear_reloadable_connections!
-
connection_pool_list.each(&:clear_reloadable_connections!)
-
end
-
-
3
def clear_all_connections!
-
10
connection_pool_list.each(&:disconnect!)
-
end
-
-
# Disconnects all currently idle connections.
-
#
-
# See ConnectionPool#flush! for details.
-
3
def flush_idle_connections!
-
connection_pool_list.each(&:flush!)
-
end
-
-
# Locate the connection of the nearest super class. This can be an
-
# active or defined connection: if it is the latter, it will be
-
# opened and set as the active connection for the class it was defined
-
# for (not necessarily the current class).
-
3
def retrieve_connection(spec_name, shard: ActiveRecord::Base.default_shard) # :nodoc:
-
266780
pool = retrieve_connection_pool(spec_name, shard: shard)
-
-
266780
unless pool
-
14
if shard != ActiveRecord::Base.default_shard
-
2
message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
-
12
elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
-
3
message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
-
else
-
9
message = "No connection pool for '#{spec_name}' found."
-
end
-
-
14
raise ConnectionNotEstablished, message
-
end
-
-
266766
pool.connection
-
end
-
-
# Returns true if a connection that's accessible to this class has
-
# already been opened.
-
3
def connected?(spec_name, shard: ActiveRecord::Base.default_shard)
-
1591
pool = retrieve_connection_pool(spec_name, shard: shard)
-
1591
pool && pool.connected?
-
end
-
-
# Remove the connection for this class. This will close the active
-
# connection and the defined connection (if they exist). The result
-
# can be used as an argument for #establish_connection, for easily
-
# re-establishing the connection.
-
3
def remove_connection(owner, shard: ActiveRecord::Base.default_shard)
-
2
remove_connection_pool(owner, shard: shard)&.configuration_hash
-
end
-
3
deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
-
-
3
def remove_connection_pool(owner, shard: ActiveRecord::Base.default_shard)
-
398
if pool_manager = get_pool_manager(owner)
-
393
pool_config = pool_manager.remove_pool_config(shard)
-
-
393
if pool_config
-
283
pool_config.disconnect!
-
283
pool_config.db_config
-
end
-
end
-
end
-
-
# Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
-
# This makes retrieving the connection pool O(1) once the process is warm.
-
# When a connection is established or removed, we invalidate the cache.
-
3
def retrieve_connection_pool(owner, shard: ActiveRecord::Base.default_shard)
-
269784
pool_config = get_pool_manager(owner)&.get_pool_config(shard)
-
269784
pool_config&.pool
-
end
-
-
3
private
-
3
attr_reader :owner_to_pool_manager
-
-
# Returns the pool manager for an owner.
-
#
-
# Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
-
# deprecated in favor of looking it up by `"ActiveRecord::Base"`.
-
#
-
# During the deprecation period, if `"primary"` is passed, the pool manager
-
# for `ActiveRecord::Base` will still be returned.
-
3
def get_pool_manager(owner)
-
270938
return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner)
-
-
24
if owner == "primary"
-
4
ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 6.2.0. Please use `ActiveRecord::Base`.")
-
4
owner_to_pool_manager[Base.name]
-
end
-
end
-
-
# Returns an instance of PoolConfig for a given adapter.
-
# Accepts a hash one layer deep that contains all connection information.
-
#
-
# == Example
-
#
-
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
-
# pool_config = Base.configurations.resolve_pool_config(:production)
-
# pool_config.db_config.configuration_hash
-
# # => { host: "localhost", database: "foo", adapter: "sqlite3" }
-
#
-
3
def resolve_pool_config(config, owner_name)
-
765
db_config = Base.configurations.resolve(config)
-
-
762
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
-
-
# Require the adapter itself and give useful feedback about
-
# 1. Missing adapter gems and
-
# 2. Adapter gems' missing dependencies.
-
762
path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
-
762
begin
-
762
require path_to_adapter
-
rescue LoadError => e
-
# We couldn't require the adapter itself. Raise an exception that
-
# points out config typos and missing gems.
-
3
if e.path == path_to_adapter
-
# We can assume that a non-builtin adapter was specified, so it's
-
# either misspelled or missing from Gemfile.
-
3
raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
-
-
# Bubbled up from the adapter require. Prefix the exception message
-
# with some guidance about how to address it and reraise.
-
else
-
raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
-
end
-
end
-
-
759
unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
-
3
raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
-
end
-
-
756
ConnectionAdapters::PoolConfig.new(owner_name, db_config)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
3
module DatabaseLimits
-
3
def max_identifier_length # :nodoc:
-
1607
64
-
end
-
-
# Returns the maximum length of a table alias.
-
3
def table_alias_length
-
1078
max_identifier_length
-
end
-
-
# Returns the maximum length of a column name.
-
3
def column_name_length
-
3
max_identifier_length
-
end
-
3
deprecate :column_name_length
-
-
# Returns the maximum length of a table name.
-
3
def table_name_length
-
3
max_identifier_length
-
end
-
3
deprecate :table_name_length
-
-
# Returns the maximum allowed length for an index name. This
-
# limit is enforced by \Rails and is less than or equal to
-
# #index_name_length. The gap between
-
# #index_name_length is to allow internal \Rails
-
# operations to use prefixes in temporary operations.
-
3
def allowed_index_name_length
-
3
index_name_length
-
end
-
3
deprecate :allowed_index_name_length
-
-
# Returns the maximum length of an index name.
-
3
def index_name_length
-
1406
max_identifier_length
-
end
-
-
# Returns the maximum number of columns per table.
-
3
def columns_per_table
-
3
1024
-
end
-
3
deprecate :columns_per_table
-
-
# Returns the maximum number of indexes per table.
-
3
def indexes_per_table
-
3
16
-
end
-
3
deprecate :indexes_per_table
-
-
# Returns the maximum number of columns in a multicolumn index.
-
3
def columns_per_multicolumn_index
-
3
16
-
end
-
3
deprecate :columns_per_multicolumn_index
-
-
# Returns the maximum number of elements in an IN (x,y,z) clause.
-
# +nil+ means no limit.
-
3
def in_clause_length
-
nil
-
end
-
3
deprecate :in_clause_length
-
-
# Returns the maximum length of an SQL query.
-
3
def sql_query_length
-
3
1048575
-
end
-
3
deprecate :sql_query_length
-
-
# Returns maximum number of joins in a single query.
-
3
def joins_per_query
-
3
256
-
end
-
3
deprecate :joins_per_query
-
-
3
private
-
3
def bind_params_length
-
18031
65535
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
3
module DatabaseStatements
-
3
def initialize
-
1052
super
-
1052
reset_transaction
-
end
-
-
# Converts an arel AST to SQL
-
3
def to_sql(arel_or_sql_string, binds = [])
-
517
sql, _ = to_sql_and_binds(arel_or_sql_string, binds)
-
517
sql
-
end
-
-
3
def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
-
59386
if arel_or_sql_string.respond_to?(:ast)
-
52346
unless binds.empty?
-
raise "Passing bind parameters with an arel AST is forbidden. " \
-
"The values must be stored on the AST directly"
-
end
-
-
52346
collector = collector()
-
-
52346
if prepared_statements
-
51854
collector.preparable = true
-
51854
sql, binds = visitor.compile(arel_or_sql_string.ast, collector)
-
-
51854
if binds.length > bind_params_length
-
11
unprepared_statement do
-
11
return to_sql_and_binds(arel_or_sql_string)
-
end
-
end
-
51843
preparable = collector.preparable
-
else
-
492
sql = visitor.compile(arel_or_sql_string.ast, collector)
-
end
-
52335
[sql.freeze, binds, preparable]
-
else
-
7040
arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen?
-
7040
[arel_or_sql_string, binds, preparable]
-
end
-
end
-
3
private :to_sql_and_binds
-
-
# This is used in the StatementCache object. It returns an object that
-
# can be used to query the database repeatedly.
-
3
def cacheable_query(klass, arel) # :nodoc:
-
1277
if prepared_statements
-
1262
sql, binds = visitor.compile(arel.ast, collector)
-
1262
query = klass.query(sql)
-
else
-
15
collector = klass.partial_query_collector
-
15
parts, binds = visitor.compile(arel.ast, collector)
-
15
query = klass.partial_query(parts)
-
end
-
1277
[query, binds]
-
end
-
-
# Returns an ActiveRecord::Result instance.
-
3
def select_all(arel, name = nil, binds = [], preparable: nil)
-
38289
arel = arel_from_relation(arel)
-
38289
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
-
-
38289
if prepared_statements && preparable
-
27053
select_prepared(sql, name, binds)
-
else
-
11236
select(sql, name, binds)
-
end
-
rescue ::RangeError
-
16
ActiveRecord::Result.new([], [])
-
end
-
-
# Returns a record hash with the column names as keys and column values
-
# as values.
-
3
def select_one(arel, name = nil, binds = [])
-
60
select_all(arel, name, binds).first
-
end
-
-
# Returns a single value from a record
-
3
def select_value(arel, name = nil, binds = [])
-
88
single_value_from_rows(select_rows(arel, name, binds))
-
end
-
-
# Returns an array of the values of the first column in a select:
-
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
-
3
def select_values(arel, name = nil, binds = [])
-
31
select_rows(arel, name, binds).map(&:first)
-
end
-
-
# Returns an array of arrays containing the field values.
-
# Order is the same as that returned by +columns+.
-
3
def select_rows(arel, name = nil, binds = [])
-
1728
select_all(arel, name, binds).rows
-
end
-
-
3
def query_value(sql, name = nil) # :nodoc:
-
56868
single_value_from_rows(query(sql, name))
-
end
-
-
3
def query_values(sql, name = nil) # :nodoc:
-
6916
query(sql, name).map(&:first)
-
end
-
-
3
def query(sql, name = nil) # :nodoc:
-
49775
exec_query(sql, name).rows
-
end
-
-
# Determines whether the SQL statement is a write query.
-
3
def write_query?(sql)
-
raise NotImplementedError
-
end
-
-
# Executes the SQL statement in the context of this connection and returns
-
# the raw result from the connection adapter.
-
# Note: depending on your database connector, the result returned by this
-
# method may be manually memory managed. Consider using the exec_query
-
# wrapper instead.
-
3
def execute(sql, name = nil)
-
raise NotImplementedError
-
end
-
-
# Executes +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
3
def exec_query(sql, name = "SQL", binds = [], prepare: false)
-
raise NotImplementedError
-
end
-
-
# Executes insert +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
3
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
-
12492
sql, binds = sql_for_insert(sql, pk, binds)
-
12492
exec_query(sql, name, binds)
-
end
-
-
# Executes delete +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
3
def exec_delete(sql, name = nil, binds = [])
-
exec_query(sql, name, binds)
-
end
-
-
# Executes update +sql+ statement in the context of this connection using
-
# +binds+ as the bind substitutes. +name+ is logged along with
-
# the executed +sql+ statement.
-
3
def exec_update(sql, name = nil, binds = [])
-
exec_query(sql, name, binds)
-
end
-
-
3
def exec_insert_all(sql, name) # :nodoc:
-
155
exec_query(sql, name)
-
end
-
-
# Executes an INSERT query and returns the new record's ID
-
#
-
# +id_value+ will be returned unless the value is +nil+, in
-
# which case the database will attempt to calculate the last inserted
-
# id and return that value.
-
#
-
# If the next id was calculated in advance (as in Oracle), it should be
-
# passed in as +id_value+.
-
3
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
-
12487
sql, binds = to_sql_and_binds(arel, binds)
-
12487
value = exec_insert(sql, name, binds, pk, sequence_name)
-
12430
id_value || last_inserted_id(value)
-
end
-
3
alias create insert
-
-
# Executes the update statement and returns the number of rows affected.
-
3
def update(arel, name = nil, binds = [])
-
4135
sql, binds = to_sql_and_binds(arel, binds)
-
4135
exec_update(sql, name, binds)
-
end
-
-
# Executes the delete statement and returns the number of rows affected.
-
3
def delete(arel, name = nil, binds = [])
-
3546
sql, binds = to_sql_and_binds(arel, binds)
-
3546
exec_delete(sql, name, binds)
-
end
-
-
# Executes the truncate statement.
-
3
def truncate(table_name, name = nil)
-
6
execute(build_truncate_statement(table_name), name)
-
end
-
-
3
def truncate_tables(*table_names) # :nodoc:
-
12
table_names -= [schema_migration.table_name, InternalMetadata.table_name]
-
-
12
return if table_names.empty?
-
-
12
with_multi_statements do
-
12
disable_referential_integrity do
-
12
statements = build_truncate_statements(table_names)
-
12
execute_batch(statements, "Truncate Tables")
-
end
-
end
-
end
-
-
# Runs the given block in a database transaction, and returns the result
-
# of the block.
-
#
-
# == Nested transactions support
-
#
-
# #transaction calls can be nested. By default, this makes all database
-
# statements in the nested transaction block become part of the parent
-
# transaction. For example, the following behavior may be surprising:
-
#
-
# ActiveRecord::Base.transaction do
-
# Post.create(title: 'first')
-
# ActiveRecord::Base.transaction do
-
# Post.create(title: 'second')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# This creates both "first" and "second" posts. Reason is the
-
# ActiveRecord::Rollback exception in the nested block does not issue a
-
# ROLLBACK. Since these exceptions are captured in transaction blocks,
-
# the parent block does not see it and the real transaction is committed.
-
#
-
# Most databases don't support true nested transactions. At the time of
-
# writing, the only database that supports true nested transactions that
-
# we're aware of, is MS-SQL.
-
#
-
# In order to get around this problem, #transaction will emulate the effect
-
# of nested transactions, by using savepoints:
-
# https://dev.mysql.com/doc/refman/en/savepoint.html.
-
#
-
# It is safe to call this method if a database transaction is already open,
-
# i.e. if #transaction is called within another #transaction block. In case
-
# of a nested call, #transaction will behave as follows:
-
#
-
# - The block will be run without doing anything. All database statements
-
# that happen within the block are effectively appended to the already
-
# open database transaction.
-
# - However, if +:requires_new+ is set, the block will be wrapped in a
-
# database savepoint acting as a sub-transaction.
-
#
-
# In order to get a ROLLBACK for the nested transaction you may ask for a
-
# real sub-transaction by passing <tt>requires_new: true</tt>.
-
# If anything goes wrong, the database rolls back to the beginning of
-
# the sub-transaction without rolling back the parent transaction.
-
# If we add it to the previous example:
-
#
-
# ActiveRecord::Base.transaction do
-
# Post.create(title: 'first')
-
# ActiveRecord::Base.transaction(requires_new: true) do
-
# Post.create(title: 'second')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# only post with title "first" is created.
-
#
-
# See ActiveRecord::Transactions to learn more.
-
#
-
# === Caveats
-
#
-
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
-
# then any created savepoints will be automatically released. For example,
-
# if you've created a savepoint, then you execute a CREATE TABLE statement,
-
# then the savepoint that was created will be automatically released.
-
#
-
# This means that, on MySQL, you shouldn't execute DDL operations inside
-
# a #transaction call that you know might create a savepoint. Otherwise,
-
# #transaction will raise exceptions when it tries to release the
-
# already-automatically-released savepoints:
-
#
-
# Model.connection.transaction do # BEGIN
-
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
-
# Model.connection.create_table(...)
-
# # active_record_1 now automatically released
-
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
-
# end
-
#
-
# == Transaction isolation
-
#
-
# If your database supports setting the isolation level for a transaction, you can set
-
# it like so:
-
#
-
# Post.transaction(isolation: :serializable) do
-
# # ...
-
# end
-
#
-
# Valid isolation levels are:
-
#
-
# * <tt>:read_uncommitted</tt>
-
# * <tt>:read_committed</tt>
-
# * <tt>:repeatable_read</tt>
-
# * <tt>:serializable</tt>
-
#
-
# You should consult the documentation for your database to understand the
-
# semantics of these different levels:
-
#
-
# * https://www.postgresql.org/docs/current/static/transaction-iso.html
-
# * https://dev.mysql.com/doc/refman/en/set-transaction.html
-
#
-
# An ActiveRecord::TransactionIsolationError will be raised if:
-
#
-
# * The adapter does not support setting the isolation level
-
# * You are joining an existing open transaction
-
# * You are creating a nested (savepoint) transaction
-
#
-
# The mysql2 and postgresql adapters support setting the transaction
-
# isolation level.
-
3
def transaction(requires_new: nil, isolation: nil, joinable: true)
-
26964
if !requires_new && current_transaction.joinable?
-
7782
if isolation
-
1
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
-
end
-
7781
yield
-
else
-
38363
transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable) { yield }
-
end
-
rescue ActiveRecord::Rollback
-
# rollbacks are silently swallowed
-
end
-
-
3
attr_reader :transaction_manager #:nodoc:
-
-
3
delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction,
-
:commit_transaction, :rollback_transaction, :materialize_transactions,
-
:disable_lazy_transactions!, :enable_lazy_transactions!, to: :transaction_manager
-
-
3
def mark_transaction_written_if_write(sql) # :nodoc:
-
316824
transaction = current_transaction
-
316824
if transaction.open?
-
188737
transaction.written ||= write_query?(sql)
-
end
-
end
-
-
3
def transaction_open?
-
128340
current_transaction.open?
-
end
-
-
3
def reset_transaction #:nodoc:
-
1993
@transaction_manager = ConnectionAdapters::TransactionManager.new(self)
-
end
-
-
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
-
# can be called.
-
3
def add_transaction_record(record, ensure_finalize = true)
-
19259
current_transaction.add_record(record, ensure_finalize)
-
end
-
-
# Begins the transaction (and turns off auto-committing).
-
3
def begin_db_transaction() end
-
-
3
def transaction_isolation_levels
-
8
{
-
read_uncommitted: "READ UNCOMMITTED",
-
read_committed: "READ COMMITTED",
-
repeatable_read: "REPEATABLE READ",
-
serializable: "SERIALIZABLE"
-
}
-
end
-
-
# Begins the transaction with the isolation level set. Raises an error by
-
# default; adapters that support setting the isolation level should implement
-
# this method.
-
3
def begin_isolated_db_transaction(isolation)
-
raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
-
end
-
-
# Commits the transaction (and turns on auto-committing).
-
3
def commit_db_transaction() end
-
-
# Rolls back the transaction (and turns on auto-committing). Must be
-
# done if the transaction block raises an exception or returns false.
-
3
def rollback_db_transaction
-
109343
exec_rollback_db_transaction
-
end
-
-
3
def exec_rollback_db_transaction() end #:nodoc:
-
-
3
def rollback_to_savepoint(name = nil)
-
317
exec_rollback_to_savepoint(name)
-
end
-
-
3
def default_sequence_name(table, column)
-
nil
-
end
-
-
# Set the sequence to the max value of the table's column.
-
3
def reset_sequence!(table, column, sequence = nil)
-
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
-
end
-
-
# Inserts the given fixture into the table. Overridden in adapters that require
-
# something beyond a simple insert (e.g. Oracle).
-
# Most of adapters should implement `insert_fixtures_set` that leverages bulk SQL insert.
-
# We keep this method to provide fallback
-
# for databases like sqlite that do not support bulk inserts.
-
3
def insert_fixture(fixture, table_name)
-
1
execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert")
-
end
-
-
3
def insert_fixtures_set(fixture_set, tables_to_delete = [])
-
1538
fixture_inserts = build_fixture_statements(fixture_set)
-
8283
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
-
1535
statements = table_deletes + fixture_inserts
-
-
1535
with_multi_statements do
-
1535
disable_referential_integrity do
-
1535
transaction(requires_new: true) do
-
1535
execute_batch(statements, "Fixtures Load")
-
end
-
end
-
end
-
end
-
-
3
def empty_insert_statement_value(primary_key = nil)
-
1294
"DEFAULT VALUES"
-
end
-
-
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
-
#
-
# The +limit+ may be anything that can evaluate to a string via #to_s. It
-
# should look like an integer, or an Arel SQL literal.
-
#
-
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
-
3
def sanitize_limit(limit)
-
20267
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
-
20255
limit
-
else
-
12
Integer(limit)
-
end
-
end
-
-
# Fixture value is quoted by Arel, however scalar values
-
# are not quotable. In this case we want to convert
-
# the column value to YAML.
-
3
def with_yaml_fallback(value) # :nodoc:
-
700068
if value.is_a?(Hash) || value.is_a?(Array)
-
283
YAML.dump(value)
-
else
-
699785
value
-
end
-
end
-
-
3
private
-
3
def execute_batch(statements, name = nil)
-
statements.each do |statement|
-
execute(statement, name)
-
end
-
end
-
-
3
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
-
3
private_constant :DEFAULT_INSERT_VALUE
-
-
3
def default_insert_value(column)
-
644295
DEFAULT_INSERT_VALUE
-
end
-
-
3
def build_fixture_sql(fixtures, table_name)
-
153450
columns = schema_cache.columns_hash(table_name)
-
-
153450
values_list = fixtures.map do |fixture|
-
295117
fixture = fixture.stringify_keys
-
-
295117
unknown_columns = fixture.keys - columns.keys
-
295117
if unknown_columns.any?
-
3
raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
-
end
-
-
295114
columns.map do |name, column|
-
1343857
if fixture.key?(name)
-
699562
type = lookup_cast_type_from_column(column)
-
699562
with_yaml_fallback(type.serialize(fixture[name]))
-
else
-
644295
default_insert_value(column)
-
end
-
end
-
end
-
-
153447
table = Arel::Table.new(table_name)
-
153447
manager = Arel::InsertManager.new
-
153447
manager.into(table)
-
-
153447
if values_list.size == 1
-
151207
values = values_list.shift
-
151207
new_values = []
-
151207
columns.each_key.with_index { |column, i|
-
699272
unless values[i].equal?(DEFAULT_INSERT_VALUE)
-
366497
new_values << values[i]
-
366497
manager.columns << table[column]
-
end
-
}
-
151207
values_list << new_values
-
else
-
21298
columns.each_key { |column| manager.columns << table[column] }
-
end
-
-
153447
manager.values = manager.create_values_list(values_list)
-
153447
visitor.compile(manager.ast)
-
end
-
-
3
def build_fixture_statements(fixture_set)
-
fixture_set.map do |table_name, fixtures|
-
2692
next if fixtures.empty?
-
2692
build_fixture_sql(fixtures, table_name)
-
603
end.compact
-
end
-
-
3
def build_truncate_statement(table_name)
-
2
"TRUNCATE TABLE #{quote_table_name(table_name)}"
-
end
-
-
3
def build_truncate_statements(table_names)
-
7
table_names.map do |table_name|
-
582
build_truncate_statement(table_name)
-
end
-
end
-
-
3
def with_multi_statements
-
1547
yield
-
end
-
-
3
def combine_multi_statements(total_sql)
-
1547
total_sql.join(";\n")
-
end
-
-
# Returns an ActiveRecord::Result instance.
-
3
def select(sql, name = nil, binds = [])
-
11236
exec_query(sql, name, binds, prepare: false)
-
end
-
-
3
def select_prepared(sql, name = nil, binds = [])
-
27053
exec_query(sql, name, binds, prepare: true)
-
end
-
-
3
def sql_for_insert(sql, pk, binds)
-
12492
[sql, binds]
-
end
-
-
3
def last_inserted_id(result)
-
4137
single_value_from_rows(result.rows)
-
end
-
-
3
def single_value_from_rows(rows)
-
61086
row = rows.first
-
61086
row && row.first
-
end
-
-
3
def arel_from_relation(relation)
-
38690
if relation.is_a?(Relation)
-
57
relation.arel
-
else
-
38633
relation
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "concurrent/map"
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
3
module QueryCache
-
3
class << self
-
3
def included(base) #:nodoc:
-
3
dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables,
-
:rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all
-
-
3
base.set_callback :checkout, :after, :configure_query_cache!
-
3
base.set_callback :checkin, :after, :disable_query_cache!
-
end
-
-
3
def dirties_query_cache(base, *method_names)
-
3
method_names.each do |method_name|
-
24
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
-
def #{method_name}(*)
-
ActiveRecord::Base.clear_query_caches_for_current_thread
-
super
-
end
-
end_code
-
end
-
end
-
end
-
-
3
module ConnectionPoolConfiguration
-
3
def initialize(*)
-
975
super
-
114475
@query_cache_enabled = Concurrent::Map.new { false }
-
end
-
-
3
def enable_query_cache!
-
581
@query_cache_enabled[connection_cache_key(current_thread)] = true
-
581
connection.enable_query_cache! if active_connection?
-
end
-
-
3
def disable_query_cache!
-
581
@query_cache_enabled.delete connection_cache_key(current_thread)
-
581
connection.disable_query_cache! if active_connection?
-
end
-
-
3
def query_cache_enabled
-
113566
@query_cache_enabled[connection_cache_key(current_thread)]
-
end
-
end
-
-
3
attr_reader :query_cache, :query_cache_enabled
-
-
3
def initialize(*)
-
1052
super
-
1316
@query_cache = Hash.new { |h, sql| h[sql] = {} }
-
1052
@query_cache_enabled = false
-
end
-
-
# Enable the query cache within the block.
-
3
def cache
-
164
old, @query_cache_enabled = @query_cache_enabled, true
-
164
yield
-
ensure
-
164
@query_cache_enabled = old
-
164
clear_query_cache unless @query_cache_enabled
-
end
-
-
3
def enable_query_cache!
-
129
@query_cache_enabled = true
-
end
-
-
3
def disable_query_cache!
-
113142
@query_cache_enabled = false
-
113142
clear_query_cache
-
end
-
-
# Disable the query cache within the block.
-
3
def uncached
-
1152
old, @query_cache_enabled = @query_cache_enabled, false
-
1152
yield
-
ensure
-
1152
@query_cache_enabled = old
-
end
-
-
# Clears the query cache.
-
#
-
# One reason you may wish to call this method explicitly is between queries
-
# that ask the database to randomize results. Otherwise the cache would see
-
# the same SQL query and repeatedly return the same result each time, silently
-
# undermining the randomness you were expecting.
-
3
def clear_query_cache
-
1067483
@lock.synchronize do
-
1067483
@query_cache.clear
-
end
-
end
-
-
3
def select_all(arel, name = nil, binds = [], preparable: nil)
-
38417
if @query_cache_enabled && !locked?(arel)
-
401
arel = arel_from_relation(arel)
-
401
sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
-
-
674
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
-
else
-
38016
super
-
end
-
end
-
-
3
private
-
3
def cache_sql(sql, name, binds)
-
401
@lock.synchronize do
-
401
result =
-
401
if @query_cache[sql].key?(binds)
-
128
ActiveSupport::Notifications.instrument(
-
"sql.active_record",
-
cache_notification_info(sql, name, binds)
-
)
-
128
@query_cache[sql][binds]
-
else
-
273
@query_cache[sql][binds] = yield
-
end
-
398
result.dup
-
end
-
end
-
-
# Database adapters can override this method to
-
# provide custom cache information.
-
3
def cache_notification_info(sql, name, binds)
-
128
{
-
sql: sql,
-
binds: binds,
-
86
type_casted_binds: -> { type_casted_binds(binds) },
-
name: name,
-
connection: self,
-
cached: true
-
}
-
end
-
-
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
-
# queries should not be cached.
-
3
def locked?(arel)
-
407
arel = arel.arel if arel.is_a?(Relation)
-
407
arel.respond_to?(:locked) && arel.locked
-
end
-
-
3
def configure_query_cache!
-
112953
enable_query_cache! if pool.query_cache_enabled
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/big_decimal/conversions"
-
3
require "active_support/multibyte/chars"
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
3
module Quoting
-
# Quotes the column value to help prevent
-
# {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
-
3
def quote(value)
-
1288061
if value.is_a?(Base)
-
3
ActiveSupport::Deprecation.warn(<<~MSG)
-
Passing an Active Record object to `quote` directly is deprecated
-
and will be no longer quoted as id value in Rails 6.2.
-
MSG
-
3
value = value.id_for_database
-
end
-
-
1288061
_quote(value)
-
end
-
-
# Cast a +value+ to a type that the database understands. For example,
-
# SQLite does not understand dates, so this method will convert a Date
-
# to a String.
-
3
def type_cast(value, column = nil)
-
309245
if value.is_a?(Base)
-
3
ActiveSupport::Deprecation.warn(<<~MSG)
-
Passing an Active Record object to `type_cast` directly is deprecated
-
and will be no longer type casted as id value in Rails 6.2.
-
MSG
-
3
value = value.id_for_database
-
end
-
-
309245
if column
-
18
ActiveSupport::Deprecation.warn(<<~MSG)
-
Passing a column to `type_cast` is deprecated and will be removed in Rails 6.2.
-
MSG
-
18
type = lookup_cast_type_from_column(column)
-
18
value = type.serialize(value)
-
end
-
-
309245
_type_cast(value)
-
end
-
-
# If you are having to call this function, you are likely doing something
-
# wrong. The column does not have sufficient type information if the user
-
# provided a custom type on the class level either explicitly (via
-
# Attributes::ClassMethods#attribute) or implicitly (via
-
# AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+).
-
# In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to
-
# represent the type doesn't sufficiently reflect the differences
-
# (varchar vs binary) for example. The type used to get this primitive
-
# should have been provided before reaching the connection adapter.
-
3
def lookup_cast_type_from_column(column) # :nodoc:
-
384692
lookup_cast_type(column.sql_type)
-
end
-
-
# Quotes a string, escaping any ' (single quote) and \ (backslash)
-
# characters.
-
3
def quote_string(s)
-
24
s.gsub('\\', '\&\&').gsub("'", "''") # ' (for ruby-mode)
-
end
-
-
# Quotes the column name. Defaults to no quoting.
-
3
def quote_column_name(column_name)
-
3259
column_name.to_s
-
end
-
-
# Quotes the table name. Defaults to column name quoting.
-
3
def quote_table_name(table_name)
-
1012
quote_column_name(table_name)
-
end
-
-
# Override to return the quoted table name for assignment. Defaults to
-
# table quoting.
-
#
-
# This works for mysql2 where table.column can be used to
-
# resolve ambiguity.
-
#
-
# We override this in the sqlite3 and postgresql adapters to use only
-
# the column name (as per syntax requirements).
-
3
def quote_table_name_for_assignment(table, attr)
-
quote_table_name("#{table}.#{attr}")
-
end
-
-
3
def quote_default_expression(value, column) # :nodoc:
-
28317
if value.is_a?(Proc)
-
value.call
-
else
-
28317
value = lookup_cast_type(column.sql_type).serialize(value)
-
28317
quote(value)
-
end
-
end
-
-
3
def quoted_true
-
1131
"TRUE"
-
end
-
-
3
def unquoted_true
-
155
true
-
end
-
-
3
def quoted_false
-
340
"FALSE"
-
end
-
-
3
def unquoted_false
-
116
false
-
end
-
-
# Quote date/time values for use in SQL input. Includes microseconds
-
# if the value is a Time responding to usec.
-
3
def quoted_date(value)
-
57326
if value.acts_like?(:time)
-
55833
if ActiveRecord::Base.default_timezone == :utc
-
55780
value = value.getutc if value.respond_to?(:getutc) && !value.utc?
-
else
-
53
value = value.getlocal if value.respond_to?(:getlocal)
-
end
-
end
-
-
57326
result = value.to_s(:db)
-
57326
if value.respond_to?(:usec) && value.usec > 0
-
49361
result << "." << sprintf("%06d", value.usec)
-
else
-
7965
result
-
end
-
end
-
-
3
def quoted_time(value) # :nodoc:
-
296
value = value.change(year: 2000, month: 1, day: 1)
-
296
quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "")
-
end
-
-
3
def quoted_binary(value) # :nodoc:
-
"'#{quote_string(value.to_s)}'"
-
end
-
-
3
def sanitize_as_sql_comment(value) # :nodoc:
-
135
value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
-
end
-
-
3
def column_name_matcher # :nodoc:
-
COLUMN_NAME
-
end
-
-
3
def column_name_with_order_matcher # :nodoc:
-
COLUMN_NAME_WITH_ORDER
-
end
-
-
# Regexp for column names (with or without a table name prefix).
-
# Matches the following:
-
#
-
# "#{table_name}.#{column_name}"
-
# "#{column_name}"
-
3
COLUMN_NAME = /
-
\A
-
(
-
(?:
-
# table_name.column_name | function(one or no argument)
-
((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
-
)
-
(?:(?:\s+AS)?\s+\w+)?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
# Regexp for column names with order (with or without a table name prefix,
-
# with or without various order modifiers). Matches the following:
-
#
-
# "#{table_name}.#{column_name}"
-
# "#{table_name}.#{column_name} #{direction}"
-
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
-
# "#{table_name}.#{column_name} NULLS LAST"
-
# "#{column_name}"
-
# "#{column_name} #{direction}"
-
# "#{column_name} #{direction} NULLS FIRST"
-
# "#{column_name} NULLS LAST"
-
3
COLUMN_NAME_WITH_ORDER = /
-
\A
-
(
-
(?:
-
# table_name.column_name | function(one or no argument)
-
((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
-
)
-
(?:\s+ASC|\s+DESC)?
-
(?:\s+NULLS\s+(?:FIRST|LAST))?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
3
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
-
-
3
private
-
3
def type_casted_binds(binds)
-
168367
case binds.first
-
when Array
-
36
binds.map { |column, value| type_cast(value, column) }
-
else
-
168349
binds.map do |value|
-
309099
if ActiveModel::Attribute === value
-
112821
type_cast(value.value_for_database)
-
else
-
196278
type_cast(value)
-
end
-
end
-
end
-
end
-
-
3
def lookup_cast_type(sql_type)
-
639400
type_map.lookup(sql_type)
-
end
-
-
3
def _quote(value)
-
1288042
case value
-
when String, Symbol, ActiveSupport::Multibyte::Chars
-
150268
"'#{quote_string(value.to_s)}'"
-
2673
when true then quoted_true
-
821
when false then quoted_false
-
18770
when nil then "NULL"
-
# BigDecimals need to be put in a non-normalized form and quoted.
-
40
when BigDecimal then value.to_s("F")
-
1070709
when Numeric, ActiveSupport::Duration then value.to_s
-
214
when Type::Binary::Data then quoted_binary(value)
-
609
when Type::Time::Value then "'#{quoted_time(value)}'"
-
43932
when Date, Time then "'#{quoted_date(value)}'"
-
3
when Class then "'#{value}'"
-
3
else raise TypeError, "can't quote #{value.class.name}"
-
end
-
end
-
-
3
def _type_cast(value)
-
309239
case value
-
when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
-
23
value.to_s
-
432
when true then unquoted_true
-
314
when false then unquoted_false
-
# BigDecimals need to be put in a non-normalized form and quoted.
-
35
when BigDecimal then value.to_s("F")
-
295681
when nil, Numeric, String then value
-
53
when Type::Time::Value then quoted_time(value)
-
12698
when Date, Time then quoted_date(value)
-
3
else raise TypeError, "can't cast #{value.class.name}"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module Savepoints
-
3
def current_savepoint_name
-
16
current_transaction.savepoint_name
-
end
-
-
3
def create_savepoint(name = current_savepoint_name)
-
11906
execute("SAVEPOINT #{name}", "TRANSACTION")
-
end
-
-
3
def exec_rollback_to_savepoint(name = current_savepoint_name)
-
317
execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
-
end
-
-
3
def release_savepoint(name = current_savepoint_name)
-
11597
execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
class SchemaCreation # :nodoc:
-
3
def initialize(conn)
-
17900
@conn = conn
-
17900
@cache = {}
-
end
-
-
3
def accept(o)
-
66089
m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
-
66089
send m, o
-
end
-
-
3
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
-
:options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options,
-
:quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?, :check_constraint_options,
-
to: :@conn, private: true
-
-
3
private
-
3
def visit_AlterTable(o)
-
771
sql = +"ALTER TABLE #{quote_table_name(o.name)} "
-
1460
sql << o.adds.map { |col| accept col }.join(" ")
-
814
sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ")
-
790
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
-
772
sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ")
-
767
sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ")
-
end
-
-
3
def visit_ColumnDefinition(o)
-
47127
o.sql_type = type_to_sql(o.type, **o.options)
-
47112
column_sql = +"#{quote_column_name(o.name)} #{o.sql_type}"
-
47112
add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
-
47112
column_sql
-
end
-
-
3
def visit_AddColumnDefinition(o)
-
716
+"ADD #{accept(o.column)}"
-
end
-
-
3
def visit_TableDefinition(o)
-
6715
create_sql = +"CREATE#{table_modifier_in_create(o)} TABLE "
-
6715
create_sql << "IF NOT EXISTS " if o.if_not_exists
-
6715
create_sql << "#{quote_table_name(o.name)} "
-
-
53117
statements = o.columns.map { |c| accept c }
-
6703
statements << accept(o.primary_keys) if o.primary_keys
-
-
6703
if supports_indexes_in_create?
-
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
-
end
-
-
6703
if supports_foreign_keys?
-
6949
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
-
end
-
-
6699
if supports_check_constraints?
-
6716
statements.concat(o.check_constraints.map { |expression, options| check_constraint_in_create(o.name, expression, options) })
-
end
-
-
6699
create_sql << "(#{statements.join(', ')})" if statements.present?
-
6699
add_table_options!(create_sql, o)
-
6699
create_sql << " AS #{to_sql(o.as)}" if o.as
-
6699
create_sql
-
end
-
-
3
def visit_PrimaryKeyDefinition(o)
-
201
"PRIMARY KEY (#{o.name.map { |name| quote_column_name(name) }.join(', ')})"
-
end
-
-
3
def visit_ForeignKeyDefinition(o)
-
292
sql = +<<~SQL
-
CONSTRAINT #{quote_column_name(o.name)}
-
FOREIGN KEY (#{quote_column_name(o.column)})
-
REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
-
SQL
-
292
sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
-
289
sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
-
286
sql
-
end
-
-
3
def visit_AddForeignKey(o)
-
46
"ADD #{accept(o)}"
-
end
-
-
3
def visit_DropForeignKey(name)
-
24
"DROP CONSTRAINT #{quote_column_name(name)}"
-
end
-
-
3
def visit_CreateIndexDefinition(o)
-
10343
index = o.index
-
-
10343
sql = ["CREATE"]
-
10343
sql << "UNIQUE" if index.unique
-
10343
sql << "INDEX"
-
10343
sql << "IF NOT EXISTS" if o.if_not_exists
-
10343
sql << o.algorithm if o.algorithm
-
10343
sql << index.type if index.type
-
10343
sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
-
10343
sql << "USING #{index.using}" if supports_index_using? && index.using
-
10343
sql << "(#{quoted_columns(index)})"
-
10343
sql << "WHERE #{index.where}" if supports_partial_index? && index.where
-
-
10343
sql.join(" ")
-
end
-
-
3
def visit_CheckConstraintDefinition(o)
-
23
"CONSTRAINT #{o.name} CHECK (#{o.expression})"
-
end
-
-
3
def visit_AddCheckConstraint(o)
-
6
"ADD #{accept(o)}"
-
end
-
-
3
def visit_DropCheckConstraint(name)
-
1
"DROP CONSTRAINT #{quote_column_name(name)}"
-
end
-
-
3
def quoted_columns(o)
-
10343
String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options)
-
end
-
-
3
def supports_index_using?
-
499
true
-
end
-
-
3
def add_table_options!(create_sql, o)
-
6699
create_sql << " #{o.options}" if o.options
-
6699
create_sql
-
end
-
-
3
def column_options(o)
-
43825
o.options.merge(column: o)
-
end
-
-
3
def add_column_options!(sql, options)
-
43790
sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
-
# must explicitly check for :null to allow change_column to work on migrations
-
43790
if options[:null] == false
-
12523
sql << " NOT NULL"
-
end
-
43790
if options[:auto_increment] == true
-
sql << " AUTO_INCREMENT"
-
end
-
43790
if options[:primary_key] == true
-
2977
sql << " PRIMARY KEY"
-
end
-
43790
sql
-
end
-
-
3
def to_sql(sql)
-
6
sql = sql.to_sql if sql.respond_to?(:to_sql)
-
6
sql
-
end
-
-
# Returns any SQL string to go between CREATE and TABLE. May be nil.
-
3
def table_modifier_in_create(o)
-
5202
" TEMPORARY" if o.temporary
-
end
-
-
3
def foreign_key_in_create(from_table, to_table, options)
-
246
prefix = ActiveRecord::Base.table_name_prefix
-
246
suffix = ActiveRecord::Base.table_name_suffix
-
246
to_table = "#{prefix}#{to_table}#{suffix}"
-
246
options = foreign_key_options(from_table, to_table, options)
-
246
accept ForeignKeyDefinition.new(from_table, to_table, options)
-
end
-
-
3
def check_constraint_in_create(table_name, expression, options)
-
17
options = check_constraint_options(table_name, expression, options)
-
17
accept CheckConstraintDefinition.new(table_name, expression, options)
-
end
-
-
3
def action_sql(action, dependency)
-
37
case dependency
-
9
when :nullify then "ON #{action} SET NULL"
-
19
when :cascade then "ON #{action} CASCADE"
-
3
when :restrict then "ON #{action} RESTRICT"
-
else
-
6
raise ArgumentError, <<~MSG
-
'#{dependency}' is not supported for :on_update or :on_delete.
-
Supported values are: :nullify, :cascade, :restrict
-
MSG
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters #:nodoc:
-
# Abstract representation of an index definition on a table. Instances of
-
# this type are typically created and returned by methods in database
-
# adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
-
3
class IndexDefinition # :nodoc:
-
3
attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment
-
-
3
def initialize(
-
table, name,
-
unique = false,
-
columns = [],
-
lengths: {},
-
orders: {},
-
opclasses: {},
-
where: nil,
-
type: nil,
-
using: nil,
-
comment: nil
-
)
-
22287
@table = table
-
22287
@name = name
-
22287
@unique = unique
-
22287
@columns = columns
-
22287
@lengths = concise_options(lengths)
-
22287
@orders = concise_options(orders)
-
22287
@opclasses = concise_options(opclasses)
-
22287
@where = where
-
22287
@type = type
-
22287
@using = using
-
22287
@comment = comment
-
end
-
-
3
def column_options
-
10308
{
-
length: lengths,
-
order: orders,
-
opclass: opclasses,
-
}
-
end
-
-
3
private
-
3
def concise_options(options)
-
66861
if columns.size == options.size && options.values.uniq.size == 1
-
75
options.values.first
-
else
-
66786
options
-
end
-
end
-
end
-
-
# Abstract representation of a column definition. Instances of this type
-
# are typically created by methods in TableDefinition, and added to the
-
# +columns+ attribute of said TableDefinition object, in order to be used
-
# for generating a number of table creation or table changing SQL statements.
-
3
ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
-
3
def primary_key?
-
9
options[:primary_key]
-
end
-
-
3
[:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name|
-
21
module_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{option_name}
-
options[:#{option_name}]
-
end
-
-
def #{option_name}=(value)
-
options[:#{option_name}] = value
-
end
-
CODE
-
end
-
end
-
-
3
AddColumnDefinition = Struct.new(:column) # :nodoc:
-
-
3
ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc:
-
-
3
CreateIndexDefinition = Struct.new(:index, :algorithm, :if_not_exists) # :nodoc:
-
-
3
PrimaryKeyDefinition = Struct.new(:name) # :nodoc:
-
-
3
ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc:
-
3
def name
-
661
options[:name]
-
end
-
-
3
def column
-
592
options[:column]
-
end
-
-
3
def primary_key
-
338
options[:primary_key] || default_primary_key
-
end
-
-
3
def on_delete
-
583
options[:on_delete]
-
end
-
-
3
def on_update
-
530
options[:on_update]
-
end
-
-
3
def custom_primary_key?
-
226
options[:primary_key] != default_primary_key
-
end
-
-
3
def validate?
-
56
options.fetch(:validate, true)
-
end
-
3
alias validated? validate?
-
-
3
def export_name_on_schema_dump?
-
226
!ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name
-
end
-
-
3
def defined_for?(to_table: nil, validate: nil, **options)
-
51
(to_table.nil? || to_table.to_s == self.to_table) &&
-
48
(validate.nil? || validate == options.fetch(:validate, validate)) &&
-
41
options.all? { |k, v| self.options[k].to_s == v.to_s }
-
end
-
-
3
private
-
3
def default_primary_key
-
482
"id"
-
end
-
end
-
-
3
CheckConstraintDefinition = Struct.new(:table_name, :expression, :options) do
-
3
def name
-
120
options[:name]
-
end
-
-
3
def export_name_on_schema_dump?
-
25
!ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
-
end
-
end
-
-
3
class ReferenceDefinition # :nodoc:
-
3
def initialize(
-
name,
-
polymorphic: false,
-
index: true,
-
foreign_key: false,
-
type: :bigint,
-
**options
-
)
-
1056
@name = name
-
1056
@polymorphic = polymorphic
-
1056
@index = index
-
1056
@foreign_key = foreign_key
-
1056
@type = type
-
1056
@options = options
-
-
1056
if polymorphic && foreign_key
-
6
raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
-
end
-
end
-
-
3
def add_to(table)
-
1050
columns.each do |name, type, options|
-
1120
table.column(name, type, **options)
-
end
-
-
1050
if index
-
811
table.index(column_names, **index_options)
-
end
-
-
1050
if foreign_key
-
150
table.foreign_key(foreign_table_name, **foreign_key_options)
-
end
-
end
-
-
3
private
-
3
attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options
-
-
3
def as_options(value)
-
1223
value.is_a?(Hash) ? value : {}
-
end
-
-
3
def polymorphic_options
-
112
as_options(polymorphic).merge(options.slice(:null, :first, :after))
-
end
-
-
3
def index_options
-
811
as_options(index)
-
end
-
-
3
def foreign_key_options
-
300
as_options(foreign_key).merge(column: column_name)
-
end
-
-
3
def columns
-
1861
result = [[column_name, type, options]]
-
1861
if polymorphic
-
112
result.unshift(["#{name}_type", :string, polymorphic_options])
-
end
-
1861
result
-
end
-
-
3
def column_name
-
2161
"#{name}_id"
-
end
-
-
3
def column_names
-
811
columns.map(&:first)
-
end
-
-
3
def foreign_table_name
-
150
foreign_key_options.fetch(:to_table) do
-
126
Base.pluralize_table_names ? name.to_s.pluralize : name
-
end
-
end
-
end
-
-
3
module ColumnMethods
-
3
extend ActiveSupport::Concern
-
-
# Appends a primary key definition to the table definition.
-
# Can be called multiple times, but this is probably not a good idea.
-
3
def primary_key(name, type = :primary_key, **options)
-
3426
column(name, type, **options.merge(primary_key: true))
-
end
-
-
##
-
# :method: column
-
# :call-seq: column(name, type, **options)
-
#
-
# Appends a column or columns of a specified type.
-
#
-
# t.string(:goat)
-
# t.string(:goat, :sheep)
-
#
-
# See TableDefinition#column
-
-
3
included do
-
6
define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
-
:float, :integer, :json, :string, :text, :time, :timestamp, :virtual
-
-
6
alias :numeric :decimal
-
end
-
-
3
class_methods do
-
3
def define_column_methods(*column_types) # :nodoc:
-
10
column_types.each do |column_type|
-
204
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{column_type}(*names, **options)
-
raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
-
names.each { |name| column(name, :#{column_type}, **options) }
-
end
-
RUBY
-
end
-
end
-
3
private :define_column_methods
-
end
-
end
-
-
# Represents the schema of an SQL table in an abstract way. This class
-
# provides methods for manipulating the schema representation.
-
#
-
# Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
-
# is actually of this type:
-
#
-
# class SomeMigration < ActiveRecord::Migration[6.0]
-
# def up
-
# create_table :foo do |t|
-
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
-
# end
-
# end
-
#
-
# def down
-
# ...
-
# end
-
# end
-
#
-
3
class TableDefinition
-
3
include ColumnMethods
-
-
3
attr_reader :name, :temporary, :if_not_exists, :options, :as, :comment, :indexes, :foreign_keys, :check_constraints
-
-
3
def initialize(
-
conn,
-
name,
-
temporary: false,
-
if_not_exists: false,
-
options: nil,
-
as: nil,
-
comment: nil,
-
**
-
)
-
7564
@conn = conn
-
7564
@columns_hash = {}
-
7564
@indexes = []
-
7564
@foreign_keys = []
-
7564
@primary_keys = nil
-
7564
@check_constraints = []
-
7564
@temporary = temporary
-
7564
@if_not_exists = if_not_exists
-
7564
@options = options
-
7564
@as = as
-
7564
@name = name
-
7564
@comment = comment
-
end
-
-
3
def primary_keys(name = nil) # :nodoc:
-
6837
@primary_keys = PrimaryKeyDefinition.new(name) if name
-
6837
@primary_keys
-
end
-
-
# Returns an array of ColumnDefinition objects for the columns of the table.
-
11104
def columns; @columns_hash.values; end
-
-
# Returns a ColumnDefinition for the column with name +name+.
-
3
def [](name)
-
78
@columns_hash[name.to_s]
-
end
-
-
# Instantiates a new column for the table.
-
# See {connection.add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column]
-
# for available options.
-
#
-
# Additional options are:
-
# * <tt>:index</tt> -
-
# Create an index for the column. Can be either <tt>true</tt> or an options hash.
-
#
-
# This method returns <tt>self</tt>.
-
#
-
# == Examples
-
#
-
# # Assuming +td+ is an instance of TableDefinition
-
# td.column(:granted, :boolean, index: true)
-
#
-
# == Short-hand examples
-
#
-
# Instead of calling #column directly, you can also work with the short-hand definitions for the default types.
-
# They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
-
# in a single statement.
-
#
-
# What can be written like this with the regular calls to column:
-
#
-
# create_table :products do |t|
-
# t.column :shop_id, :integer
-
# t.column :creator_id, :integer
-
# t.column :item_number, :string
-
# t.column :name, :string, default: "Untitled"
-
# t.column :value, :string, default: "Untitled"
-
# t.column :created_at, :datetime
-
# t.column :updated_at, :datetime
-
# end
-
# add_index :products, :item_number
-
#
-
# can also be written as follows using the short-hand:
-
#
-
# create_table :products do |t|
-
# t.integer :shop_id, :creator_id
-
# t.string :item_number, index: true
-
# t.string :name, :value, default: "Untitled"
-
# t.timestamps null: false
-
# end
-
#
-
# There's a short-hand method for each of the type values declared at the top. And then there's
-
# TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
-
#
-
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
-
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
-
# options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
-
# will also create an index, similar to calling {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index].
-
# So what can be written like this:
-
#
-
# create_table :taggings do |t|
-
# t.integer :tag_id, :tagger_id, :taggable_id
-
# t.string :tagger_type
-
# t.string :taggable_type, default: 'Photo'
-
# end
-
# add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id'
-
# add_index :taggings, [:tagger_id, :tagger_type]
-
#
-
# Can also be written as follows using references:
-
#
-
# create_table :taggings do |t|
-
# t.references :tag, index: { name: 'index_taggings_on_tag_id' }
-
# t.references :tagger, polymorphic: true
-
# t.references :taggable, polymorphic: { default: 'Photo' }, index: false
-
# end
-
3
def column(name, type, index: nil, **options)
-
46640
name = name.to_s
-
46640
type = type.to_sym if type
-
-
46640
if @columns_hash[name]
-
9
if @columns_hash[name].primary_key?
-
6
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
-
else
-
3
raise ArgumentError, "you can't define an already defined column '#{name}'."
-
end
-
end
-
-
46631
@columns_hash[name] = new_column_definition(name, type, **options)
-
-
46631
if index
-
9
index_options = index.is_a?(Hash) ? index : {}
-
9
index(name, **index_options)
-
end
-
-
46631
self
-
end
-
-
# remove the column +name+ from the table.
-
# remove_column(:account_id)
-
3
def remove_column(name)
-
1164
@columns_hash.delete name.to_s
-
end
-
-
# Adds index options to the indexes hash, keyed by column name
-
# This is primarily used to track indexes that need to be created after the table
-
#
-
# index(:account_id, name: 'index_projects_on_account_id')
-
3
def index(column_name, **options)
-
955
indexes << [column_name, options]
-
end
-
-
3
def foreign_key(table_name, **options) # :nodoc:
-
254
foreign_keys << [table_name, options]
-
end
-
-
3
def check_constraint(expression, **options)
-
17
check_constraints << [expression, options]
-
end
-
-
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
-
# <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
-
#
-
# t.timestamps null: false
-
3
def timestamps(**options)
-
343
options[:null] = false if options[:null].nil?
-
-
343
if !options.key?(:precision) && @conn.supports_datetime_with_precision?
-
324
options[:precision] = 6
-
end
-
-
343
column(:created_at, :datetime, **options)
-
343
column(:updated_at, :datetime, **options)
-
end
-
-
# Adds a reference.
-
#
-
# t.references(:user)
-
# t.belongs_to(:supplier, foreign_key: true)
-
# t.belongs_to(:supplier, foreign_key: true, type: :integer)
-
#
-
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
-
3
def references(*args, **options)
-
981
args.each do |ref_name|
-
981
ReferenceDefinition.new(ref_name, **options).add_to(self)
-
end
-
end
-
3
alias :belongs_to :references
-
-
3
def new_column_definition(name, type, **options) # :nodoc:
-
47382
if integer_like_primary_key?(type, options)
-
53
type = integer_like_primary_key_type(type, options)
-
end
-
47382
type = aliased_types(type.to_s, type)
-
47382
options[:primary_key] ||= type == :primary_key
-
47382
options[:null] = false if options[:primary_key]
-
47382
create_column_definition(name, type, options)
-
end
-
-
3
private
-
3
def create_column_definition(name, type, options)
-
47382
ColumnDefinition.new(name, type, options)
-
end
-
-
3
def aliased_types(name, fallback)
-
47382
"timestamp" == name ? :datetime : fallback
-
end
-
-
3
def integer_like_primary_key?(type, options)
-
47382
options[:primary_key] && [:integer, :bigint].include?(type) && !options.key?(:default)
-
end
-
-
3
def integer_like_primary_key_type(type, options)
-
type
-
end
-
end
-
-
3
class AlterTable # :nodoc:
-
3
attr_reader :adds
-
3
attr_reader :foreign_key_adds, :foreign_key_drops
-
3
attr_reader :check_constraint_adds, :check_constraint_drops
-
-
3
def initialize(td)
-
771
@td = td
-
771
@adds = []
-
771
@foreign_key_adds = []
-
771
@foreign_key_drops = []
-
771
@check_constraint_adds = []
-
771
@check_constraint_drops = []
-
end
-
-
826
def name; @td.name; end
-
-
3
def add_foreign_key(to_table, options)
-
46
@foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options)
-
end
-
-
3
def drop_foreign_key(name)
-
24
@foreign_key_drops << name
-
end
-
-
3
def add_check_constraint(expression, options)
-
6
@check_constraint_adds << CheckConstraintDefinition.new(name, expression, options)
-
end
-
-
3
def drop_check_constraint(constraint_name)
-
1
@check_constraint_drops << constraint_name
-
end
-
-
3
def add_column(name, type, **options)
-
689
name = name.to_s
-
689
type = type.to_sym
-
689
@adds << AddColumnDefinition.new(@td.new_column_definition(name, type, **options))
-
end
-
end
-
-
# Represents an SQL table in an abstract way for updating a table.
-
# Also see TableDefinition and {connection.create_table}[rdoc-ref:SchemaStatements#create_table]
-
#
-
# Available transformations are:
-
#
-
# change_table :table do |t|
-
# t.primary_key
-
# t.column
-
# t.index
-
# t.rename_index
-
# t.timestamps
-
# t.change
-
# t.change_default
-
# t.change_null
-
# t.rename
-
# t.references
-
# t.belongs_to
-
# t.check_constraint
-
# t.string
-
# t.text
-
# t.integer
-
# t.bigint
-
# t.float
-
# t.decimal
-
# t.numeric
-
# t.datetime
-
# t.timestamp
-
# t.time
-
# t.date
-
# t.binary
-
# t.boolean
-
# t.foreign_key
-
# t.json
-
# t.virtual
-
# t.remove
-
# t.remove_foreign_key
-
# t.remove_references
-
# t.remove_belongs_to
-
# t.remove_index
-
# t.remove_check_constraint
-
# t.remove_timestamps
-
# end
-
#
-
3
class Table
-
3
include ColumnMethods
-
-
3
attr_reader :name
-
-
3
def initialize(table_name, base)
-
309
@name = table_name
-
309
@base = base
-
end
-
-
# Adds a new column to the named table.
-
#
-
# t.column(:name, :string)
-
#
-
# See TableDefinition#column for details of the options you can use.
-
3
def column(column_name, type, index: nil, **options)
-
173
@base.add_column(name, column_name, type, **options)
-
173
if index
-
3
index_options = index.is_a?(Hash) ? index : {}
-
3
index(column_name, **index_options)
-
end
-
end
-
-
# Checks to see if a column exists.
-
#
-
# t.string(:name) unless t.column_exists?(:name, :string)
-
#
-
# See {connection.column_exists?}[rdoc-ref:SchemaStatements#column_exists?]
-
3
def column_exists?(column_name, type = nil, **options)
-
6
@base.column_exists?(name, column_name, type, **options)
-
end
-
-
# Adds a new index to the table. +column_name+ can be a single Symbol, or
-
# an Array of Symbols.
-
#
-
# t.index(:name)
-
# t.index([:branch_id, :party_id], unique: true)
-
# t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
-
#
-
# See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use.
-
3
def index(column_name, **options)
-
81
@base.add_index(name, column_name, **options)
-
end
-
-
# Checks to see if an index exists.
-
#
-
# unless t.index_exists?(:branch_id)
-
# t.index(:branch_id)
-
# end
-
#
-
# See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
-
3
def index_exists?(column_name, options = {})
-
6
@base.index_exists?(name, column_name, options)
-
end
-
-
# Renames the given index on the table.
-
#
-
# t.rename_index(:user_id, :account_id)
-
#
-
# See {connection.rename_index}[rdoc-ref:SchemaStatements#rename_index]
-
3
def rename_index(index_name, new_index_name)
-
3
@base.rename_index(name, index_name, new_index_name)
-
end
-
-
# Adds timestamps (+created_at+ and +updated_at+) columns to the table.
-
#
-
# t.timestamps(null: false)
-
#
-
# See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
-
3
def timestamps(**options)
-
20
@base.add_timestamps(name, **options)
-
end
-
-
# Changes the column's definition according to the new options.
-
#
-
# t.change(:name, :string, limit: 80)
-
# t.change(:description, :text)
-
#
-
# See TableDefinition#column for details of the options you can use.
-
3
def change(column_name, type, **options)
-
8
@base.change_column(name, column_name, type, **options)
-
end
-
-
# Sets a new default value for a column.
-
#
-
# t.change_default(:qualification, 'new')
-
# t.change_default(:authorized, 1)
-
# t.change_default(:status, from: nil, to: "draft")
-
#
-
# See {connection.change_column_default}[rdoc-ref:SchemaStatements#change_column_default]
-
3
def change_default(column_name, default_or_changes)
-
3
@base.change_column_default(name, column_name, default_or_changes)
-
end
-
-
# Sets or removes a NOT NULL constraint on a column.
-
#
-
# t.change_null(:qualification, true)
-
# t.change_null(:qualification, false, 0)
-
#
-
# See {connection.change_column_null}[rdoc-ref:SchemaStatements#change_column_null]
-
3
def change_null(column_name, null, default = nil)
-
3
@base.change_column_null(name, column_name, null, default)
-
end
-
-
# Removes the column(s) from the table definition.
-
#
-
# t.remove(:qualification)
-
# t.remove(:qualification, :experience)
-
#
-
# See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns]
-
3
def remove(*column_names, **options)
-
19
@base.remove_columns(name, *column_names, **options)
-
end
-
-
# Removes the given index from the table.
-
#
-
# t.remove_index(:branch_id)
-
# t.remove_index(column: [:branch_id, :party_id])
-
# t.remove_index(name: :by_branch_party)
-
# t.remove_index(:branch_id, name: :by_branch_party)
-
#
-
# See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index]
-
3
def remove_index(column_name = nil, **options)
-
17
@base.remove_index(name, column_name, **options)
-
end
-
-
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
-
#
-
# t.remove_timestamps
-
#
-
# See {connection.remove_timestamps}[rdoc-ref:SchemaStatements#remove_timestamps]
-
3
def remove_timestamps(**options)
-
3
@base.remove_timestamps(name, **options)
-
end
-
-
# Renames a column.
-
#
-
# t.rename(:description, :name)
-
#
-
# See {connection.rename_column}[rdoc-ref:SchemaStatements#rename_column]
-
3
def rename(column_name, new_column_name)
-
7
@base.rename_column(name, column_name, new_column_name)
-
end
-
-
# Adds a reference.
-
#
-
# t.references(:user)
-
# t.belongs_to(:supplier, foreign_key: true)
-
#
-
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
-
3
def references(*args, **options)
-
54
args.each do |ref_name|
-
54
@base.add_reference(name, ref_name, **options)
-
end
-
end
-
3
alias :belongs_to :references
-
-
# Removes a reference. Optionally removes a +type+ column.
-
#
-
# t.remove_references(:user)
-
# t.remove_belongs_to(:supplier, polymorphic: true)
-
#
-
# See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
-
3
def remove_references(*args, **options)
-
15
args.each do |ref_name|
-
15
@base.remove_reference(name, ref_name, **options)
-
end
-
end
-
3
alias :remove_belongs_to :remove_references
-
-
# Adds a foreign key to the table using a supplied table name.
-
#
-
# t.foreign_key(:authors)
-
# t.foreign_key(:authors, column: :author_id, primary_key: "id")
-
#
-
# See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
-
3
def foreign_key(*args, **options)
-
18
@base.add_foreign_key(name, *args, **options)
-
end
-
-
# Removes the given foreign key from the table.
-
#
-
# t.remove_foreign_key(:authors)
-
# t.remove_foreign_key(column: :author_id)
-
#
-
# See {connection.remove_foreign_key}[rdoc-ref:SchemaStatements#remove_foreign_key]
-
3
def remove_foreign_key(*args, **options)
-
9
@base.remove_foreign_key(name, *args, **options)
-
end
-
-
# Checks to see if a foreign key exists.
-
#
-
# t.foreign_key(:authors) unless t.foreign_key_exists?(:authors)
-
#
-
# See {connection.foreign_key_exists?}[rdoc-ref:SchemaStatements#foreign_key_exists?]
-
3
def foreign_key_exists?(*args, **options)
-
8
@base.foreign_key_exists?(name, *args, **options)
-
end
-
-
# Adds a check constraint.
-
#
-
# t.check_constraint("price > 0", name: "price_check")
-
#
-
# See {connection.add_check_constraint}[rdoc-ref:SchemaStatements#add_check_constraint]
-
3
def check_constraint(*args)
-
3
@base.add_check_constraint(name, *args)
-
end
-
-
# Removes the given check constraint from the table.
-
#
-
# t.remove_check_constraint(name: "price_check")
-
#
-
# See {connection.remove_check_constraint}[rdoc-ref:SchemaStatements#remove_check_constraint]
-
3
def remove_check_constraint(*args)
-
3
@base.remove_check_constraint(name, *args)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
3
class SchemaDumper < SchemaDumper # :nodoc:
-
3
def self.create(connection, options)
-
197
new(connection, options)
-
end
-
-
3
private
-
3
def column_spec(column)
-
12909
[schema_type_with_virtual(column), prepare_column_options(column)]
-
end
-
-
3
def column_spec_for_primary_key(column)
-
4020
spec = {}
-
4020
spec[:id] = schema_type(column).inspect unless default_primary_key?(column)
-
4020
spec.merge!(prepare_column_options(column).except!(:null))
-
4020
spec[:default] ||= "nil" if explicit_primary_key_default?(column)
-
4020
spec
-
end
-
-
3
def prepare_column_options(column)
-
16929
spec = {}
-
16929
spec[:limit] = schema_limit(column)
-
16929
spec[:precision] = schema_precision(column)
-
16929
spec[:scale] = schema_scale(column)
-
16929
spec[:default] = schema_default(column)
-
16929
spec[:null] = "false" unless column.null
-
16929
spec[:collation] = schema_collation(column)
-
16929
spec[:comment] = column.comment.inspect if column.comment.present?
-
16929
spec.compact!
-
16929
spec
-
end
-
-
3
def default_primary_key?(column)
-
schema_type(column) == :bigint
-
end
-
-
3
def explicit_primary_key_default?(column)
-
false
-
end
-
-
3
def schema_type_with_virtual(column)
-
12909
if @connection.supports_virtual_columns? && column.virtual?
-
:virtual
-
else
-
12909
schema_type(column)
-
end
-
end
-
-
3
def schema_type(column)
-
15367
if column.bigint?
-
811
:bigint
-
else
-
14556
column.type
-
end
-
end
-
-
3
def schema_limit(column)
-
16929
limit = column.limit unless column.bigint?
-
16929
limit.inspect if limit && limit != @connection.native_database_types[column.type][:limit]
-
end
-
-
3
def schema_precision(column)
-
16929
column.precision.inspect if column.precision
-
end
-
-
3
def schema_scale(column)
-
16929
column.scale.inspect if column.scale
-
end
-
-
3
def schema_default(column)
-
16929
return unless column.has_default?
-
3333
type = @connection.lookup_cast_type_from_column(column)
-
3333
default = type.deserialize(column.default)
-
3333
if default.nil?
-
1877
schema_expression(column)
-
else
-
1456
type.type_cast_for_schema(default)
-
end
-
end
-
-
3
def schema_expression(column)
-
103
"-> { #{column.default_function.inspect} }" if column.default_function
-
end
-
-
3
def schema_collation(column)
-
16929
column.collation.inspect if column.collation
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/access"
-
3
require "digest/sha2"
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
3
module SchemaStatements
-
3
include ActiveRecord::Migration::JoinTable
-
-
# Returns a hash of mappings from the abstract data types to the native
-
# database types. See TableDefinition#column for details on the recognized
-
# abstract data types.
-
3
def native_database_types
-
{}
-
end
-
-
3
def table_options(table_name)
-
nil
-
end
-
-
# Returns the table comment that's stored in database metadata.
-
3
def table_comment(table_name)
-
nil
-
end
-
-
# Truncates a table alias according to the limits of the current adapter.
-
3
def table_alias_for(table_name)
-
1031
table_name[0...table_alias_length].tr(".", "_")
-
end
-
-
# Returns the relation names useable to back Active Record models.
-
# For most adapters this means all #tables and #views.
-
3
def data_sources
-
506
query_values(data_source_sql, "SCHEMA")
-
rescue NotImplementedError
-
tables | views
-
end
-
-
# Checks to see if the data source +name+ exists on the database.
-
#
-
# data_source_exists?(:ebooks)
-
#
-
3
def data_source_exists?(name)
-
990
query_values(data_source_sql(name), "SCHEMA").any? if name.present?
-
rescue NotImplementedError
-
data_sources.include?(name.to_s)
-
end
-
-
# Returns an array of table names defined in the database.
-
3
def tables
-
1451
query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
-
end
-
-
# Checks to see if the table +table_name+ exists on the database.
-
#
-
# table_exists?(:developers)
-
#
-
3
def table_exists?(table_name)
-
316
query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
-
rescue NotImplementedError
-
tables.include?(table_name.to_s)
-
end
-
-
# Returns an array of view names defined in the database.
-
3
def views
-
7
query_values(data_source_sql(type: "VIEW"), "SCHEMA")
-
end
-
-
# Checks to see if the view +view_name+ exists on the database.
-
#
-
# view_exists?(:ebooks)
-
#
-
3
def view_exists?(view_name)
-
70
query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
-
rescue NotImplementedError
-
views.include?(view_name.to_s)
-
end
-
-
# Returns an array of indexes for the given table.
-
3
def indexes(table_name)
-
raise NotImplementedError, "#indexes is not implemented"
-
end
-
-
# Checks to see if an index exists on a table for a given index definition.
-
#
-
# # Check an index exists
-
# index_exists?(:suppliers, :company_id)
-
#
-
# # Check an index on multiple columns exists
-
# index_exists?(:suppliers, [:company_id, :company_type])
-
#
-
# # Check a unique index exists
-
# index_exists?(:suppliers, :company_id, unique: true)
-
#
-
# # Check an index with a custom name exists
-
# index_exists?(:suppliers, :company_id, name: "idx_company_id")
-
#
-
3
def index_exists?(table_name, column_name, **options)
-
147
checks = []
-
-
147
if column_name.present?
-
144
column_names = Array(column_name).map(&:to_s)
-
260
checks << lambda { |i| Array(i.columns) == column_names }
-
end
-
-
156
checks << lambda { |i| i.unique } if options[:unique]
-
208
checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
-
-
452
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
-
end
-
-
# Returns an array of +Column+ objects for the table specified by +table_name+.
-
3
def columns(table_name)
-
25680
table_name = table_name.to_s
-
25680
column_definitions(table_name).map do |field|
-
247260
new_column_from_field(table_name, field)
-
end
-
end
-
-
# Checks to see if a column exists in a given table.
-
#
-
# # Check a column exists
-
# column_exists?(:suppliers, :name)
-
#
-
# # Check a column exists of a particular type
-
# column_exists?(:suppliers, :name, :string)
-
#
-
# # Check a column exists with a specific definition
-
# column_exists?(:suppliers, :name, :string, limit: 100)
-
# column_exists?(:suppliers, :name, :string, default: 'default')
-
# column_exists?(:suppliers, :name, :string, null: false)
-
# column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
-
#
-
3
def column_exists?(table_name, column_name, type = nil, **options)
-
211
column_name = column_name.to_s
-
211
checks = []
-
1047
checks << lambda { |c| c.name == column_name }
-
286
checks << lambda { |c| c.type == type.to_sym rescue nil } if type
-
211
column_options_keys.each do |attr|
-
1640
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
-
end
-
-
2121
columns(table_name).any? { |c| checks.all? { |check| check[c] } }
-
end
-
-
# Returns just a table's primary key
-
3
def primary_key(table_name)
-
11352
pk = primary_keys(table_name)
-
11352
pk = pk.first unless pk.size > 1
-
11352
pk
-
end
-
-
# Creates a new table with the name +table_name+. +table_name+ may either
-
# be a String or a Symbol.
-
#
-
# There are two ways to work with #create_table. You can use the block
-
# form or the regular form, like this:
-
#
-
# === Block form
-
#
-
# # create_table() passes a TableDefinition object to the block.
-
# # This form will not only create the table, but also columns for the
-
# # table.
-
#
-
# create_table(:suppliers) do |t|
-
# t.column :name, :string, limit: 60
-
# # Other fields here
-
# end
-
#
-
# === Block form, with shorthand
-
#
-
# # You can also use the column types as method calls, rather than calling the column method.
-
# create_table(:suppliers) do |t|
-
# t.string :name, limit: 60
-
# # Other fields here
-
# end
-
#
-
# === Regular form
-
#
-
# # Creates a table called 'suppliers' with no columns.
-
# create_table(:suppliers)
-
# # Add a column to 'suppliers'.
-
# add_column(:suppliers, :name, :string, {limit: 60})
-
#
-
# The +options+ hash can include the following keys:
-
# [<tt>:id</tt>]
-
# Whether to automatically add a primary key column. Defaults to true.
-
# Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
-
#
-
# A Symbol can be used to specify the type of the generated primary key column.
-
# [<tt>:primary_key</tt>]
-
# The name of the primary key, if one is to be added automatically.
-
# Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
-
#
-
# If an array is passed, a composite primary key will be created.
-
#
-
# Note that Active Record models will automatically detect their
-
# primary key. This can be avoided by using
-
# {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
-
# to define the key explicitly.
-
#
-
# [<tt>:options</tt>]
-
# Any extra options you want appended to the table definition.
-
# [<tt>:temporary</tt>]
-
# Make a temporary table.
-
# [<tt>:force</tt>]
-
# Set to true to drop the table before creating it.
-
# Set to +:cascade+ to drop dependent objects as well.
-
# Defaults to false.
-
# [<tt>:if_not_exists</tt>]
-
# Set to true to avoid raising an error when the table already exists.
-
# Defaults to false.
-
# [<tt>:as</tt>]
-
# SQL to use to generate the table. When this option is used, the block is
-
# ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
-
#
-
# ====== Add a backend specific option to the generated SQL (MySQL)
-
#
-
# create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
-
#
-
# generates:
-
#
-
# CREATE TABLE suppliers (
-
# id bigint auto_increment PRIMARY KEY
-
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
-
#
-
# ====== Rename the primary key column
-
#
-
# create_table(:objects, primary_key: 'guid') do |t|
-
# t.column :name, :string, limit: 80
-
# end
-
#
-
# generates:
-
#
-
# CREATE TABLE objects (
-
# guid bigint auto_increment PRIMARY KEY,
-
# name varchar(80)
-
# )
-
#
-
# ====== Change the primary key column type
-
#
-
# create_table(:tags, id: :string) do |t|
-
# t.column :label, :string
-
# end
-
#
-
# generates:
-
#
-
# CREATE TABLE tags (
-
# id varchar PRIMARY KEY,
-
# label varchar
-
# )
-
#
-
# ====== Create a composite primary key
-
#
-
# create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
-
# t.belongs_to :product
-
# t.belongs_to :client
-
# end
-
#
-
# generates:
-
#
-
# CREATE TABLE order (
-
# product_id bigint NOT NULL,
-
# client_id bigint NOT NULL
-
# );
-
#
-
# ALTER TABLE ONLY "orders"
-
# ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
-
#
-
# ====== Do not add a primary key column
-
#
-
# create_table(:categories_suppliers, id: false) do |t|
-
# t.column :category_id, :bigint
-
# t.column :supplier_id, :bigint
-
# end
-
#
-
# generates:
-
#
-
# CREATE TABLE categories_suppliers (
-
# category_id bigint,
-
# supplier_id bigint
-
# )
-
#
-
# ====== Create a temporary table based on a query
-
#
-
# create_table(:long_query, temporary: true,
-
# as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
-
#
-
# generates:
-
#
-
# CREATE TEMPORARY TABLE long_query AS
-
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
-
#
-
# See also TableDefinition#column for details on how to create columns.
-
3
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
-
6731
td = create_table_definition(table_name, **extract_table_options!(options))
-
-
6731
if id && !td.as
-
3416
pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
-
-
3416
if id.is_a?(Hash)
-
options.merge!(id.except(:type))
-
id = id.fetch(:type, :primary_key)
-
end
-
-
3416
if pk.is_a?(Array)
-
63
td.primary_keys pk
-
else
-
3353
td.primary_key pk, id, **options
-
end
-
end
-
-
6731
yield td if block_given?
-
-
6716
if force
-
2246
drop_table(table_name, force: force, if_exists: true)
-
else
-
4470
schema_cache.clear_data_source_cache!(table_name.to_s)
-
end
-
-
6715
result = execute schema_creation.accept td
-
-
6696
unless supports_indexes_in_create?
-
6696
td.indexes.each do |column_name, index_options|
-
955
add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists)
-
end
-
end
-
-
6696
if supports_comments? && !supports_comments_in_create?
-
1506
if table_comment = td.comment.presence
-
35
change_table_comment(table_name, table_comment)
-
end
-
-
1506
td.columns.each do |column|
-
4873
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
-
end
-
end
-
-
6696
result
-
end
-
-
# Creates a new join table with the name created using the lexical order of the first two
-
# arguments. These arguments can be a String or a Symbol.
-
#
-
# # Creates a table called 'assemblies_parts' with no id.
-
# create_join_table(:assemblies, :parts)
-
#
-
# You can pass an +options+ hash which can include the following keys:
-
# [<tt>:table_name</tt>]
-
# Sets the table name, overriding the default.
-
# [<tt>:column_options</tt>]
-
# Any extra options you want appended to the columns definition.
-
# [<tt>:options</tt>]
-
# Any extra options you want appended to the table definition.
-
# [<tt>:temporary</tt>]
-
# Make a temporary table.
-
# [<tt>:force</tt>]
-
# Set to true to drop the table before creating it.
-
# Defaults to false.
-
#
-
# Note that #create_join_table does not create any indices by default; you can use
-
# its block form to do so yourself:
-
#
-
# create_join_table :products, :categories do |t|
-
# t.index :product_id
-
# t.index :category_id
-
# end
-
#
-
# ====== Add a backend specific option to the generated SQL (MySQL)
-
#
-
# create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
-
#
-
# generates:
-
#
-
# CREATE TABLE assemblies_parts (
-
# assembly_id bigint NOT NULL,
-
# part_id bigint NOT NULL,
-
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
-
#
-
3
def create_join_table(table_1, table_2, column_options: {}, **options)
-
67
join_table_name = find_join_table_name(table_1, table_2, options)
-
-
67
column_options.reverse_merge!(null: false, index: false)
-
-
201
t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
-
-
67
create_table(join_table_name, **options.merge!(id: false)) do |td|
-
67
td.references t1_ref, **column_options
-
67
td.references t2_ref, **column_options
-
67
yield td if block_given?
-
end
-
end
-
-
# Drops the join table specified by the given arguments.
-
# See #create_join_table for details.
-
#
-
# Although this command ignores the block if one is given, it can be helpful
-
# to provide one in a migration's +change+ method so it can be reverted.
-
# In that case, the block will be used by #create_join_table.
-
3
def drop_join_table(table_1, table_2, **options)
-
33
join_table_name = find_join_table_name(table_1, table_2, options)
-
33
drop_table(join_table_name)
-
end
-
-
# A block for changing columns in +table+.
-
#
-
# # change_table() yields a Table instance
-
# change_table(:suppliers) do |t|
-
# t.column :name, :string, limit: 60
-
# # Other column alterations here
-
# end
-
#
-
# The +options+ hash can include the following keys:
-
# [<tt>:bulk</tt>]
-
# Set this to true to make this a bulk alter query, such as
-
#
-
# ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
-
#
-
# Defaults to false.
-
#
-
# Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
-
#
-
# ====== Add a column
-
#
-
# change_table(:suppliers) do |t|
-
# t.column :name, :string, limit: 60
-
# end
-
#
-
# ====== Change type of a column
-
#
-
# change_table(:suppliers) do |t|
-
# t.change :metadata, :json
-
# end
-
#
-
# ====== Add 2 integer columns
-
#
-
# change_table(:suppliers) do |t|
-
# t.integer :width, :height, null: false, default: 0
-
# end
-
#
-
# ====== Add created_at/updated_at columns
-
#
-
# change_table(:suppliers) do |t|
-
# t.timestamps
-
# end
-
#
-
# ====== Add a foreign key column
-
#
-
# change_table(:suppliers) do |t|
-
# t.references :company
-
# end
-
#
-
# Creates a <tt>company_id(bigint)</tt> column.
-
#
-
# ====== Add a polymorphic foreign key column
-
#
-
# change_table(:suppliers) do |t|
-
# t.belongs_to :company, polymorphic: true
-
# end
-
#
-
# Creates <tt>company_type(varchar)</tt> and <tt>company_id(bigint)</tt> columns.
-
#
-
# ====== Remove a column
-
#
-
# change_table(:suppliers) do |t|
-
# t.remove :company
-
# end
-
#
-
# ====== Remove several columns
-
#
-
# change_table(:suppliers) do |t|
-
# t.remove :company_id
-
# t.remove :width, :height
-
# end
-
#
-
# ====== Remove an index
-
#
-
# change_table(:suppliers) do |t|
-
# t.remove_index :company_id
-
# end
-
#
-
# See also Table for details on all of the various column transformations.
-
3
def change_table(table_name, **options)
-
105
if supports_bulk_alter? && options[:bulk]
-
17
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
-
17
yield update_table_definition(table_name, recorder)
-
17
bulk_change_table(table_name, recorder.commands)
-
else
-
88
yield update_table_definition(table_name, self)
-
end
-
end
-
-
# Renames a table.
-
#
-
# rename_table('octopuses', 'octopi')
-
#
-
3
def rename_table(table_name, new_name)
-
raise NotImplementedError, "rename_table is not implemented"
-
end
-
-
# Drops a table from the database.
-
#
-
# [<tt>:force</tt>]
-
# Set to +:cascade+ to drop dependent objects as well.
-
# Defaults to false.
-
# [<tt>:if_exists</tt>]
-
# Set to +true+ to only drop the table if it exists.
-
# Defaults to false.
-
#
-
# Although this command ignores most +options+ and the block if one is given,
-
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
-
# In that case, +options+ and the block will be used by #create_table.
-
3
def drop_table(table_name, **options)
-
6684
schema_cache.clear_data_source_cache!(table_name.to_s)
-
6684
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
-
end
-
-
# Add a new +type+ column named +column_name+ to +table_name+.
-
#
-
# The +type+ parameter is normally one of the migrations native types,
-
# which is one of the following:
-
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
-
# <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
-
# <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
-
# <tt>:binary</tt>, <tt>:boolean</tt>.
-
#
-
# You may use a type not in this list as long as it is supported by your
-
# database (for example, "polygon" in MySQL), but this will not be database
-
# agnostic and should usually be avoided.
-
#
-
# Available options are (none of these exists by default):
-
# * <tt>:limit</tt> -
-
# Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
-
# and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
-
# This option is ignored by some backends.
-
# * <tt>:default</tt> -
-
# The column's default value. Use +nil+ for +NULL+.
-
# * <tt>:null</tt> -
-
# Allows or disallows +NULL+ values in the column.
-
# * <tt>:precision</tt> -
-
# Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
-
# <tt>:datetime</tt>, and <tt>:time</tt> columns.
-
# * <tt>:scale</tt> -
-
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
-
# * <tt>:collation</tt> -
-
# Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
-
# column will have the same collation as the table.
-
# * <tt>:comment</tt> -
-
# Specifies the comment for the column. This option is ignored by some backends.
-
# * <tt>:if_not_exists</tt> -
-
# Specifies if the column already exists to not try to re-add it. This will avoid
-
# duplicate column errors.
-
#
-
# Note: The precision is the total number of significant digits,
-
# and the scale is the number of digits that can be stored following
-
# the decimal point. For example, the number 123.45 has a precision of 5
-
# and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
-
# range from -999.99 to 999.99.
-
#
-
# Please be aware of different RDBMS implementations behavior with
-
# <tt>:decimal</tt> columns:
-
# * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
-
# <tt>:precision</tt>, and makes no comments about the requirements of
-
# <tt>:precision</tt>.
-
# * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
-
# Default is (10,0).
-
# * PostgreSQL: <tt>:precision</tt> [1..infinity],
-
# <tt>:scale</tt> [0..infinity]. No default.
-
# * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
-
# but the maximum supported <tt>:precision</tt> is 16. No default.
-
# * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
-
# Default is (38,0).
-
# * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
-
# Default (38,0).
-
#
-
# == Examples
-
#
-
# add_column(:users, :picture, :binary, limit: 2.megabytes)
-
# # ALTER TABLE "users" ADD "picture" blob(2097152)
-
#
-
# add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
-
# # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
-
#
-
# add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
-
# # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
-
#
-
# add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
-
# # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
-
#
-
# # While :scale defaults to zero on most databases, it
-
# # probably wouldn't hurt to include it.
-
# add_column(:measurements, :huge_integer, :decimal, precision: 30)
-
# # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
-
#
-
# # Defines a column that stores an array of a type.
-
# add_column(:users, :skills, :text, array: true)
-
# # ALTER TABLE "users" ADD "skills" text[]
-
#
-
# # Defines a column with a database-specific type.
-
# add_column(:shapes, :triangle, 'polygon')
-
# # ALTER TABLE "shapes" ADD "triangle" polygon
-
#
-
# # Ignores the method call if the column exists
-
# add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
-
3
def add_column(table_name, column_name, type, **options)
-
692
return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type)
-
-
689
at = create_alter_table table_name
-
689
at.add_column(column_name, type, **options)
-
689
execute schema_creation.accept at
-
end
-
-
3
def add_columns(table_name, *column_names, type:, **options) # :nodoc:
-
3
column_names.each do |column_name|
-
3
add_column(table_name, column_name, type, **options)
-
end
-
end
-
-
# Removes the given columns from the table definition.
-
#
-
# remove_columns(:suppliers, :qualification, :experience)
-
#
-
# +type+ and other column options can be passed to make migration reversible.
-
#
-
# remove_columns(:suppliers, :qualification, :experience, type: :string, null: false)
-
3
def remove_columns(table_name, *column_names, type: nil, **options)
-
3
if column_names.empty?
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
-
end
-
-
3
column_names.each do |column_name|
-
3
remove_column(table_name, column_name, type, **options)
-
end
-
end
-
-
# Removes the column from the table definition.
-
#
-
# remove_column(:suppliers, :qualification)
-
#
-
# The +type+ and +options+ parameters will be ignored if present. It can be helpful
-
# to provide these in a migration's +change+ method so it can be reverted.
-
# In that case, +type+ and +options+ will be used by #add_column.
-
# Indexes on the column are automatically removed.
-
#
-
# If the options provided include an +if_exists+ key, it will be used to check if the
-
# column does not exist. This will silently ignore the migration rather than raising
-
# if the column was already used.
-
#
-
# remove_column(:suppliers, :qualification, if_exists: true)
-
3
def remove_column(table_name, column_name, type = nil, **options)
-
697
return if options[:if_exists] == true && !column_exists?(table_name, column_name)
-
-
696
execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}"
-
end
-
-
# Changes the column's definition according to the new options.
-
# See TableDefinition#column for details of the options you can use.
-
#
-
# change_column(:suppliers, :name, :string, limit: 80)
-
# change_column(:accounts, :description, :text)
-
#
-
3
def change_column(table_name, column_name, type, **options)
-
raise NotImplementedError, "change_column is not implemented"
-
end
-
-
# Sets a new default value for a column:
-
#
-
# change_column_default(:suppliers, :qualification, 'new')
-
# change_column_default(:accounts, :authorized, 1)
-
#
-
# Setting the default to +nil+ effectively drops the default:
-
#
-
# change_column_default(:users, :email, nil)
-
#
-
# Passing a hash containing +:from+ and +:to+ will make this change
-
# reversible in migration:
-
#
-
# change_column_default(:posts, :state, from: nil, to: "draft")
-
#
-
3
def change_column_default(table_name, column_name, default_or_changes)
-
raise NotImplementedError, "change_column_default is not implemented"
-
end
-
-
# Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
-
# indicates whether the value can be +NULL+. For example
-
#
-
# change_column_null(:users, :nickname, false)
-
#
-
# says nicknames cannot be +NULL+ (adds the constraint), whereas
-
#
-
# change_column_null(:users, :nickname, true)
-
#
-
# allows them to be +NULL+ (drops the constraint).
-
#
-
# The method accepts an optional fourth argument to replace existing
-
# <tt>NULL</tt>s with some other value. Use that one when enabling the
-
# constraint if needed, since otherwise those rows would not be valid.
-
#
-
# Please note the fourth argument does not set a column's default.
-
3
def change_column_null(table_name, column_name, null, default = nil)
-
raise NotImplementedError, "change_column_null is not implemented"
-
end
-
-
# Renames a column.
-
#
-
# rename_column(:suppliers, :description, :name)
-
#
-
3
def rename_column(table_name, column_name, new_column_name)
-
raise NotImplementedError, "rename_column is not implemented"
-
end
-
-
# Adds a new index to the table. +column_name+ can be a single Symbol, or
-
# an Array of Symbols.
-
#
-
# The index will be named after the table and the column name(s), unless
-
# you pass <tt>:name</tt> as an option.
-
#
-
# ====== Creating a simple index
-
#
-
# add_index(:suppliers, :name)
-
#
-
# generates:
-
#
-
# CREATE INDEX index_suppliers_on_name ON suppliers(name)
-
#
-
# ====== Creating a index which already exists
-
#
-
# add_index(:suppliers, :name, if_not_exists: true)
-
#
-
# generates:
-
#
-
# CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name)
-
#
-
# Note: Not supported by MySQL.
-
#
-
# ====== Creating a unique index
-
#
-
# add_index(:accounts, [:branch_id, :party_id], unique: true)
-
#
-
# generates:
-
#
-
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id)
-
#
-
# ====== Creating a named index
-
#
-
# add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
-
#
-
# generates:
-
#
-
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
-
#
-
# ====== Creating an index with specific key length
-
#
-
# add_index(:accounts, :name, name: 'by_name', length: 10)
-
#
-
# generates:
-
#
-
# CREATE INDEX by_name ON accounts(name(10))
-
#
-
# ====== Creating an index with specific key lengths for multiple keys
-
#
-
# add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
-
#
-
# generates:
-
#
-
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
-
#
-
# Note: SQLite doesn't support index length.
-
#
-
# ====== Creating an index with a sort order (desc or asc, asc is the default)
-
#
-
# add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc})
-
#
-
# generates:
-
#
-
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
-
#
-
# Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
-
#
-
# ====== Creating a partial index
-
#
-
# add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
-
#
-
# generates:
-
#
-
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
-
#
-
# Note: Partial indexes are only supported for PostgreSQL and SQLite.
-
#
-
# ====== Creating an index with a specific method
-
#
-
# add_index(:developers, :name, using: 'btree')
-
#
-
# generates:
-
#
-
# CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
-
# CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
-
#
-
# Note: only supported by PostgreSQL and MySQL
-
#
-
# ====== Creating an index with a specific operator class
-
#
-
# add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
-
# # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
-
#
-
# add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
-
# # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
-
#
-
# add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
-
# # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
-
#
-
# Note: only supported by PostgreSQL
-
#
-
# ====== Creating an index with a specific type
-
#
-
# add_index(:developers, :name, type: :fulltext)
-
#
-
# generates:
-
#
-
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
-
#
-
# Note: only supported by MySQL.
-
#
-
# ====== Creating an index with a specific algorithm
-
#
-
# add_index(:developers, :name, algorithm: :concurrently)
-
# # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
-
#
-
# Note: only supported by PostgreSQL.
-
#
-
# Concurrently adding an index is not supported in a transaction.
-
#
-
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
-
3
def add_index(table_name, column_name, **options)
-
9848
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
-
-
9844
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
-
9844
execute schema_creation.accept(create_index)
-
end
-
-
# Removes the given index from the table.
-
#
-
# Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
-
#
-
# remove_index :accounts, :branch_id
-
#
-
# Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
-
#
-
# remove_index :accounts, column: :branch_id
-
#
-
# Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
-
#
-
# remove_index :accounts, column: [:branch_id, :party_id]
-
#
-
# Removes the index named +by_branch_party+ in the +accounts+ table.
-
#
-
# remove_index :accounts, name: :by_branch_party
-
#
-
# Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table.
-
#
-
# remove_index :accounts, :branch_id, name: :by_branch_party
-
#
-
# Checks if the index exists before trying to remove it. Will silently ignore indexes that
-
# don't exist.
-
#
-
# remove_index :accounts, if_exists: true
-
#
-
# Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
-
#
-
# remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
-
#
-
# Note: only supported by PostgreSQL.
-
#
-
# Concurrently removing an index is not supported in a transaction.
-
#
-
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
-
3
def remove_index(table_name, column_name = nil, **options)
-
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
-
-
index_name = index_name_for_remove(table_name, column_name, options)
-
-
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
-
end
-
-
# Renames an index.
-
#
-
# Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
-
#
-
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
-
#
-
3
def rename_index(table_name, old_name, new_name)
-
22
validate_index_length!(table_name, new_name)
-
-
# this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
-
40
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
-
20
return unless old_index_def
-
20
add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
-
20
remove_index(table_name, name: old_name)
-
end
-
-
3
def index_name(table_name, options) #:nodoc:
-
2743
if Hash === options
-
1403
if options[:column]
-
1403
"index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
-
elsif options[:name]
-
options[:name]
-
else
-
raise ArgumentError, "You must specify the index name"
-
end
-
else
-
1340
index_name(table_name, index_name_options(options))
-
end
-
end
-
-
# Verifies the existence of an index with a given name.
-
3
def index_name_exists?(table_name, index_name)
-
22
index_name = index_name.to_s
-
42
indexes(table_name).detect { |i| i.name == index_name }
-
end
-
-
# Adds a reference. The reference column is a bigint by default,
-
# the <tt>:type</tt> option can be used to specify a different type.
-
# Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
-
# #add_reference and #add_belongs_to are acceptable.
-
#
-
# The +options+ hash can include the following keys:
-
# [<tt>:type</tt>]
-
# The reference column type. Defaults to +:bigint+.
-
# [<tt>:index</tt>]
-
# Add an appropriate index. Defaults to true.
-
# See #add_index for usage of this option.
-
# [<tt>:foreign_key</tt>]
-
# Add an appropriate foreign key constraint. Defaults to false, pass true
-
# to add. In case the join table can't be inferred from the association
-
# pass <tt>:to_table</tt> with the appropriate table name.
-
# [<tt>:polymorphic</tt>]
-
# Whether an additional +_type+ column should be added. Defaults to false.
-
# [<tt>:null</tt>]
-
# Whether the column allows nulls. Defaults to true.
-
#
-
# ====== Create a user_id bigint column without an index
-
#
-
# add_reference(:products, :user, index: false)
-
#
-
# ====== Create a user_id string column
-
#
-
# add_reference(:products, :user, type: :string)
-
#
-
# ====== Create supplier_id, supplier_type columns
-
#
-
# add_reference(:products, :supplier, polymorphic: true)
-
#
-
# ====== Create a supplier_id column with a unique index
-
#
-
# add_reference(:products, :supplier, index: { unique: true })
-
#
-
# ====== Create a supplier_id column with a named index
-
#
-
# add_reference(:products, :supplier, index: { name: "my_supplier_index" })
-
#
-
# ====== Create a supplier_id column and appropriate foreign key
-
#
-
# add_reference(:products, :supplier, foreign_key: true)
-
#
-
# ====== Create a supplier_id column and a foreign key to the firms table
-
#
-
# add_reference(:products, :supplier, foreign_key: { to_table: :firms })
-
#
-
3
def add_reference(table_name, ref_name, **options)
-
75
ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
-
end
-
3
alias :add_belongs_to :add_reference
-
-
# Removes the reference(s). Also removes a +type+ column if one exists.
-
# #remove_reference and #remove_belongs_to are acceptable.
-
#
-
# ====== Remove the reference
-
#
-
# remove_reference(:products, :user, index: false)
-
#
-
# ====== Remove polymorphic reference
-
#
-
# remove_reference(:products, :supplier, polymorphic: true)
-
#
-
# ====== Remove the reference with a foreign key
-
#
-
# remove_reference(:products, :user, foreign_key: true)
-
#
-
3
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
-
30
if foreign_key
-
12
reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
-
12
if foreign_key.is_a?(Hash)
-
6
foreign_key_options = foreign_key
-
else
-
6
foreign_key_options = { to_table: reference_name }
-
end
-
12
foreign_key_options[:column] ||= "#{ref_name}_id"
-
12
remove_foreign_key(table_name, **foreign_key_options)
-
end
-
-
30
remove_column(table_name, "#{ref_name}_id")
-
30
remove_column(table_name, "#{ref_name}_type") if polymorphic
-
end
-
3
alias :remove_belongs_to :remove_reference
-
-
# Returns an array of foreign keys for the given table.
-
# The foreign keys are represented as ForeignKeyDefinition objects.
-
3
def foreign_keys(table_name)
-
raise NotImplementedError, "foreign_keys is not implemented"
-
end
-
-
# Adds a new foreign key. +from_table+ is the table with the key column,
-
# +to_table+ contains the referenced primary key.
-
#
-
# The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
-
# +identifier+ is a 10 character long string which is deterministically generated from the
-
# +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
-
#
-
# ====== Creating a simple foreign key
-
#
-
# add_foreign_key :articles, :authors
-
#
-
# generates:
-
#
-
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
-
#
-
# ====== Creating a foreign key on a specific column
-
#
-
# add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
-
#
-
# generates:
-
#
-
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
-
#
-
# ====== Creating a cascading foreign key
-
#
-
# add_foreign_key :articles, :authors, on_delete: :cascade
-
#
-
# generates:
-
#
-
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
-
#
-
# The +options+ hash can include the following keys:
-
# [<tt>:column</tt>]
-
# The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
-
# [<tt>:primary_key</tt>]
-
# The primary key column name on +to_table+. Defaults to +id+.
-
# [<tt>:name</tt>]
-
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
-
# [<tt>:on_delete</tt>]
-
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
-
# [<tt>:on_update</tt>]
-
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
-
# [<tt>:validate</tt>]
-
# (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
-
3
def add_foreign_key(from_table, to_table, **options)
-
46
return unless supports_foreign_keys?
-
-
46
options = foreign_key_options(from_table, to_table, options)
-
46
at = create_alter_table from_table
-
46
at.add_foreign_key to_table, options
-
-
46
execute schema_creation.accept(at)
-
end
-
-
# Removes the given foreign key from the table. Any option parameters provided
-
# will be used to re-add the foreign key in case of a migration rollback.
-
# It is recommended that you provide any options used when creating the foreign
-
# key so that the migration can be reverted properly.
-
#
-
# Removes the foreign key on +accounts.branch_id+.
-
#
-
# remove_foreign_key :accounts, :branches
-
#
-
# Removes the foreign key on +accounts.owner_id+.
-
#
-
# remove_foreign_key :accounts, column: :owner_id
-
#
-
# Removes the foreign key on +accounts.owner_id+.
-
#
-
# remove_foreign_key :accounts, to_table: :owners
-
#
-
# Removes the foreign key named +special_fk_name+ on the +accounts+ table.
-
#
-
# remove_foreign_key :accounts, name: :special_fk_name
-
#
-
# The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
-
# with an addition of
-
# [<tt>:to_table</tt>]
-
# The name of the table that contains the referenced primary key.
-
3
def remove_foreign_key(from_table, to_table = nil, **options)
-
25
return unless supports_foreign_keys?
-
-
25
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
-
-
24
at = create_alter_table from_table
-
24
at.drop_foreign_key fk_name_to_delete
-
-
24
execute schema_creation.accept(at)
-
end
-
-
# Checks to see if a foreign key exists on a table for a given foreign key definition.
-
#
-
# # Checks to see if a foreign key exists.
-
# foreign_key_exists?(:accounts, :branches)
-
#
-
# # Checks to see if a foreign key on a specified column exists.
-
# foreign_key_exists?(:accounts, column: :owner_id)
-
#
-
# # Checks to see if a foreign key with a custom name exists.
-
# foreign_key_exists?(:accounts, name: "special_fk_name")
-
#
-
3
def foreign_key_exists?(from_table, to_table = nil, **options)
-
22
foreign_key_for(from_table, to_table: to_table, **options).present?
-
end
-
-
3
def foreign_key_column_for(table_name) # :nodoc:
-
274
name = strip_table_name_prefix_and_suffix(table_name)
-
274
"#{name.singularize}_id"
-
end
-
-
3
def foreign_key_options(from_table, to_table, options) # :nodoc:
-
292
options = options.dup
-
292
options[:column] ||= foreign_key_column_for(to_table)
-
292
options[:name] ||= foreign_key_name(from_table, options)
-
292
options
-
end
-
-
# Returns an array of check constraints for the given table.
-
# The check constraints are represented as CheckConstraintDefinition objects.
-
3
def check_constraints(table_name)
-
raise NotImplementedError
-
end
-
-
# Adds a new check constraint to the table. +expression+ is a String
-
# representation of verifiable boolean condition.
-
#
-
# add_check_constraint :products, "price > 0", name: "price_check"
-
#
-
# generates:
-
#
-
# ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
-
#
-
3
def add_check_constraint(table_name, expression, **options)
-
6
return unless supports_check_constraints?
-
-
6
options = check_constraint_options(table_name, expression, options)
-
6
at = create_alter_table(table_name)
-
6
at.add_check_constraint(expression, options)
-
-
6
execute schema_creation.accept(at)
-
end
-
-
3
def check_constraint_options(table_name, expression, options) # :nodoc:
-
23
options = options.dup
-
23
options[:name] ||= check_constraint_name(table_name, expression: expression, **options)
-
23
options
-
end
-
-
# Removes the given check constraint from the table.
-
#
-
# remove_check_constraint :products, name: "price_check"
-
#
-
# The +expression+ parameter will be ignored if present. It can be helpful
-
# to provide this in a migration's +change+ method so it can be reverted.
-
# In that case, +expression+ will be used by #add_check_constraint.
-
3
def remove_check_constraint(table_name, expression = nil, **options)
-
2
return unless supports_check_constraints?
-
-
2
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
-
-
1
at = create_alter_table(table_name)
-
1
at.drop_check_constraint(chk_name_to_delete)
-
-
1
execute schema_creation.accept(at)
-
end
-
-
3
def dump_schema_information # :nodoc:
-
6
versions = schema_migration.all_versions
-
6
insert_versions_sql(versions) if versions.any?
-
end
-
-
3
def internal_string_options_for_primary_key # :nodoc:
-
46
{ primary_key: true }
-
end
-
-
3
def assume_migrated_upto_version(version, migrations_paths = nil)
-
15
unless migrations_paths.nil?
-
3
ActiveSupport::Deprecation.warn(<<~MSG.squish)
-
Passing migrations_paths to #assume_migrated_upto_version is deprecated and will be removed in Rails 6.1.
-
MSG
-
end
-
-
15
version = version.to_i
-
15
sm_table = quote_table_name(schema_migration.table_name)
-
-
15
migrated = migration_context.get_all_versions
-
15
versions = migration_context.migrations.map(&:version)
-
-
15
unless migrated.include?(version)
-
15
execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
-
end
-
-
24
inserting = (versions - migrated).select { |v| v < version }
-
15
if inserting.any?
-
9
if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
-
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
-
end
-
3
execute insert_versions_sql(inserting)
-
end
-
end
-
-
3
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
-
46574
type = type.to_sym if type
-
46574
if native = native_database_types[type]
-
46065
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
-
-
46065
if type == :decimal # ignore limit, use precision and scale
-
217
scale ||= native[:scale]
-
-
217
if precision ||= native[:precision]
-
162
if scale
-
142
column_type_sql << "(#{precision},#{scale})"
-
else
-
20
column_type_sql << "(#{precision})"
-
end
-
55
elsif scale
-
3
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
-
end
-
-
45848
elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
-
5570
if (0..6) === precision
-
5564
column_type_sql << "(#{precision})"
-
else
-
6
raise ArgumentError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6"
-
end
-
40278
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
-
2765
column_type_sql << "(#{limit})"
-
end
-
-
46056
column_type_sql
-
else
-
509
type.to_s
-
end
-
end
-
-
# Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
-
# PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
-
# require the order columns appear in the SELECT.
-
#
-
# columns_for_distinct("posts.id", ["posts.created_at desc"])
-
#
-
3
def columns_for_distinct(columns, orders) # :nodoc:
-
261
columns
-
end
-
-
# Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
-
# Additional options (like +:null+) are forwarded to #add_column.
-
#
-
# add_timestamps(:suppliers, null: true)
-
#
-
3
def add_timestamps(table_name, **options)
-
24
options[:null] = false if options[:null].nil?
-
-
24
if !options.key?(:precision) && supports_datetime_with_precision?
-
12
options[:precision] = 6
-
end
-
-
24
add_column table_name, :created_at, :datetime, **options
-
24
add_column table_name, :updated_at, :datetime, **options
-
end
-
-
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
-
#
-
# remove_timestamps(:suppliers)
-
#
-
3
def remove_timestamps(table_name, **options)
-
remove_column table_name, :updated_at
-
remove_column table_name, :created_at
-
end
-
-
3
def update_table_definition(table_name, base) #:nodoc:
-
190
Table.new(table_name, base)
-
end
-
-
3
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
-
10351
options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
-
-
10348
column_names = index_column_names(column_name)
-
-
10348
index_name = name&.to_s
-
10348
index_name ||= index_name(table_name, column_names)
-
-
10348
validate_index_length!(table_name, index_name, internal)
-
-
10345
index = IndexDefinition.new(
-
table_name, index_name,
-
options[:unique],
-
column_names,
-
lengths: options[:length] || {},
-
orders: options[:order] || {},
-
opclasses: options[:opclass] || {},
-
where: options[:where],
-
type: options[:type],
-
using: options[:using],
-
comment: options[:comment]
-
)
-
-
10345
[index, index_algorithm(options[:algorithm]), if_not_exists]
-
end
-
-
3
def index_algorithm(algorithm) # :nodoc:
-
9
index_algorithms.fetch(algorithm) do
-
2
raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}"
-
10391
end if algorithm
-
end
-
-
3
def quoted_columns_for_index(column_names, options) # :nodoc:
-
10308
quoted_columns = column_names.each_with_object({}) do |name, result|
-
10575
result[name.to_sym] = quote_column_name(name).dup
-
end
-
10308
add_options_for_index_columns(quoted_columns, **options).values.join(", ")
-
end
-
-
3
def options_include_default?(options)
-
43790
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
-
end
-
-
# Changes the comment for a table or removes it if +nil+.
-
#
-
# Passing a hash containing +:from+ and +:to+ will make this change
-
# reversible in migration:
-
#
-
# change_table_comment(:posts, from: "old_comment", to: "new_comment")
-
3
def change_table_comment(table_name, comment_or_changes)
-
raise NotImplementedError, "#{self.class} does not support changing table comments"
-
end
-
-
# Changes the comment for a column or removes it if +nil+.
-
#
-
# Passing a hash containing +:from+ and +:to+ will make this change
-
# reversible in migration:
-
#
-
# change_column_comment(:posts, :state, from: "old_comment", to: "new_comment")
-
3
def change_column_comment(table_name, column_name, comment_or_changes)
-
raise NotImplementedError, "#{self.class} does not support changing column comments"
-
end
-
-
3
def create_schema_dumper(options) # :nodoc:
-
SchemaDumper.create(self, options)
-
end
-
-
3
private
-
3
def column_options_keys
-
211
[:limit, :precision, :scale, :default, :null, :collation, :comment]
-
end
-
-
3
def add_index_sort_order(quoted_columns, **options)
-
10308
orders = options_for_index_columns(options[:order])
-
10308
quoted_columns.each do |name, column|
-
10575
column << " #{orders[name].upcase}" if orders[name].present?
-
end
-
end
-
-
3
def options_for_index_columns(options)
-
10796
if options.is_a?(Hash)
-
10780
options.symbolize_keys
-
else
-
42
Hash.new { |hash, column| hash[column] = options }
-
end
-
end
-
-
# Overridden by the MySQL adapter for supporting index lengths and by
-
# the PostgreSQL adapter for supporting operator classes.
-
3
def add_options_for_index_columns(quoted_columns, **options)
-
10308
if supports_index_sort_order?
-
10308
quoted_columns = add_index_sort_order(quoted_columns, **options)
-
end
-
-
10308
quoted_columns
-
end
-
-
3
def index_name_for_remove(table_name, column_name, options)
-
145
return options[:name] if can_remove_index_by_name?(column_name, options)
-
-
91
checks = []
-
-
115
checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
-
91
column_names = index_column_names(column_name || options[:column])
-
-
91
if column_names.present?
-
165
checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
-
end
-
-
91
raise ArgumentError, "No name or columns specified" if checks.none?
-
-
285
matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } }
-
-
90
if matching_indexes.count > 1
-
raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
-
"Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
-
90
elsif matching_indexes.none?
-
15
raise ArgumentError, "No indexes found on #{table_name} with the options provided."
-
else
-
75
matching_indexes.first.name
-
end
-
end
-
-
3
def rename_table_indexes(table_name, new_name)
-
30
indexes(new_name).each do |index|
-
15
generated_index_name = index_name(table_name, column: index.columns)
-
15
if generated_index_name == index.name
-
9
rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
-
end
-
end
-
end
-
-
3
def rename_column_indexes(table_name, column_name, new_column_name)
-
56
column_name, new_column_name = column_name.to_s, new_column_name.to_s
-
56
indexes(table_name).each do |index|
-
34
next unless index.columns.include?(new_column_name)
-
21
old_columns = index.columns.dup
-
21
old_columns[old_columns.index(new_column_name)] = column_name
-
21
generated_index_name = index_name(table_name, column: old_columns)
-
21
if generated_index_name == index.name
-
18
rename_index table_name, generated_index_name, index_name(table_name, column: index.columns)
-
end
-
end
-
end
-
-
3
def schema_creation
-
9
SchemaCreation.new(self)
-
end
-
-
3
def create_table_definition(name, **options)
-
TableDefinition.new(self, name, **options)
-
end
-
-
3
def create_alter_table(name)
-
379
AlterTable.new create_table_definition(name)
-
end
-
-
3
def extract_table_options!(options)
-
6731
options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
-
end
-
-
3
def fetch_type_metadata(sql_type)
-
226391
cast_type = lookup_cast_type(sql_type)
-
226391
SqlTypeMetadata.new(
-
sql_type: sql_type,
-
type: cast_type.type,
-
limit: cast_type.limit,
-
precision: cast_type.precision,
-
scale: cast_type.scale,
-
)
-
end
-
-
3
def index_column_names(column_names)
-
10439
if column_names.is_a?(String) && /\W/.match?(column_names)
-
35
column_names
-
else
-
10404
Array(column_names)
-
end
-
end
-
-
3
def index_name_options(column_names)
-
1340
if column_names.is_a?(String) && /\W/.match?(column_names)
-
13
column_names = column_names.scan(/\w+/).join("_")
-
end
-
-
1340
{ column: column_names }
-
end
-
-
3
def strip_table_name_prefix_and_suffix(table_name)
-
476
prefix = Base.table_name_prefix
-
476
suffix = Base.table_name_suffix
-
476
table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
-
end
-
-
3
def foreign_key_name(table_name, options)
-
272
options.fetch(:name) do
-
272
identifier = "#{table_name}_#{options.fetch(:column)}_fk"
-
272
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
-
-
272
"fk_rails_#{hashed_identifier}"
-
end
-
end
-
-
3
def foreign_key_for(from_table, **options)
-
52
return unless supports_foreign_keys?
-
103
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
-
end
-
-
3
def foreign_key_for!(from_table, to_table: nil, **options)
-
30
foreign_key_for(from_table, to_table: to_table, **options) ||
-
raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
-
end
-
-
3
def extract_foreign_key_action(specifier)
-
608
case specifier
-
24
when "CASCADE"; :cascade
-
6
when "SET NULL"; :nullify
-
2
when "RESTRICT"; :restrict
-
end
-
end
-
-
3
def check_constraint_name(table_name, **options)
-
9
options.fetch(:name) do
-
3
expression = options.fetch(:expression)
-
3
identifier = "#{table_name}_#{expression}_chk"
-
3
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
-
-
3
"chk_rails_#{hashed_identifier}"
-
end
-
end
-
-
3
def check_constraint_for(table_name, **options)
-
6
return unless supports_check_constraints?
-
6
chk_name = check_constraint_name(table_name, **options)
-
12
check_constraints(table_name).detect { |chk| chk.name == chk_name }
-
end
-
-
3
def check_constraint_for!(table_name, expression: nil, **options)
-
6
check_constraint_for(table_name, expression: expression, **options) ||
-
raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}")
-
end
-
-
3
def validate_index_length!(table_name, new_name, internal = false)
-
1367
if new_name.length > index_name_length
-
6
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
-
end
-
end
-
-
3
def extract_new_default_value(default_or_changes)
-
141
if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
-
13
default_or_changes[:to]
-
else
-
128
default_or_changes
-
end
-
end
-
3
alias :extract_new_comment_value :extract_new_default_value
-
-
3
def can_remove_index_by_name?(column_name, options)
-
145
column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
-
end
-
-
3
def bulk_change_table(table_name, operations)
-
17
sql_fragments = []
-
17
non_combinable_operations = []
-
-
17
operations.each do |command, args|
-
34
table, arguments = args.shift, args
-
34
method = :"#{command}_for_alter"
-
-
34
if respond_to?(method, true)
-
58
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
-
25
sql_fragments << sqls
-
25
non_combinable_operations.concat(procs)
-
else
-
9
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
-
9
non_combinable_operations.each(&:call)
-
9
sql_fragments = []
-
9
non_combinable_operations = []
-
9
send(command, table, *arguments)
-
end
-
end
-
-
17
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
-
17
non_combinable_operations.each(&:call)
-
end
-
-
3
def add_column_for_alter(table_name, column_name, type, **options)
-
27
td = create_table_definition(table_name)
-
27
cd = td.new_column_definition(column_name, type, **options)
-
27
schema_creation.accept(AddColumnDefinition.new(cd))
-
end
-
-
3
def rename_column_sql(table_name, column_name, new_column_name)
-
21
"RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
-
end
-
-
3
def remove_column_for_alter(table_name, column_name, type = nil, **options)
-
698
"DROP COLUMN #{quote_column_name(column_name)}"
-
end
-
-
3
def remove_columns_for_alter(table_name, *column_names, **options)
-
3
column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
-
end
-
-
3
def add_timestamps_for_alter(table_name, **options)
-
5
options[:null] = false if options[:null].nil?
-
-
5
if !options.key?(:precision) && supports_datetime_with_precision?
-
3
options[:precision] = 6
-
end
-
-
5
[
-
add_column_for_alter(table_name, :created_at, :datetime, **options),
-
add_column_for_alter(table_name, :updated_at, :datetime, **options)
-
]
-
end
-
-
3
def remove_timestamps_for_alter(table_name, **options)
-
remove_columns_for_alter(table_name, :updated_at, :created_at)
-
end
-
-
3
def insert_versions_sql(versions)
-
6
sm_table = quote_table_name(schema_migration.table_name)
-
-
6
if versions.is_a?(Array)
-
6
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
-
21
sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
-
6
sql << ";\n\n"
-
6
sql
-
else
-
"INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
-
end
-
end
-
-
3
def data_source_sql(name = nil, type: nil)
-
raise NotImplementedError
-
end
-
-
3
def quoted_scope(name = nil, type: nil)
-
raise NotImplementedError
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
class TransactionState
-
3
def initialize(state = nil)
-
128520
@state = state
-
128520
@children = nil
-
end
-
-
3
def add_child(state)
-
12745
@children ||= []
-
12745
@children << state
-
end
-
-
3
def finalized?
-
@state
-
end
-
-
3
def committed?
-
36
@state == :committed || @state == :fully_committed
-
end
-
-
3
def fully_committed?
-
@state == :fully_committed
-
end
-
-
3
def rolledback?
-
27
@state == :rolledback || @state == :fully_rolledback
-
end
-
-
3
def fully_rolledback?
-
@state == :fully_rolledback
-
end
-
-
3
def fully_completed?
-
completed?
-
end
-
-
3
def completed?
-
20
committed? || rolledback?
-
end
-
-
3
def rollback!
-
13512
@children&.each { |c| c.rollback! }
-
13476
@state = :rolledback
-
end
-
-
3
def full_rollback!
-
122077
@children&.each { |c| c.rollback! }
-
109424
@state = :fully_rolledback
-
end
-
-
3
def commit!
-
11965
@state = :committed
-
end
-
-
3
def full_commit!
-
6159
@state = :fully_committed
-
end
-
-
3
def nullify!
-
4
@state = nil
-
end
-
end
-
-
3
class NullTransaction #:nodoc:
-
3
def initialize; end
-
3
def state; end
-
3
def closed?; true; end
-
129827
def open?; false; end
-
119465
def joinable?; false; end
-
3
def add_record(record, _ = true); end
-
end
-
-
3
class Transaction #:nodoc:
-
3
attr_reader :connection, :state, :savepoint_name, :isolation_level
-
3
attr_accessor :written
-
-
3
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
-
128520
@connection = connection
-
128520
@state = TransactionState.new
-
128520
@records = nil
-
128520
@isolation_level = isolation
-
128520
@materialized = false
-
128520
@joinable = joinable
-
128520
@run_commit_callbacks = run_commit_callbacks
-
128520
@lazy_enrollment_records = nil
-
end
-
-
3
def add_record(record, ensure_finalize = true)
-
19255
@records ||= []
-
19255
if ensure_finalize
-
2742
@records << record
-
else
-
16513
@lazy_enrollment_records ||= ObjectSpace::WeakMap.new
-
16513
@lazy_enrollment_records[record] = record
-
end
-
end
-
-
3
def records
-
174193
if @lazy_enrollment_records
-
13026
@records.concat @lazy_enrollment_records.values
-
13026
@lazy_enrollment_records = nil
-
end
-
174193
@records
-
end
-
-
3
def materialize!
-
127283
@materialized = true
-
end
-
-
3
def materialized?
-
158597
@materialized
-
end
-
-
3
def rollback_records
-
110199
return unless records
-
971
ite = records.uniq(&:__id__)
-
971
already_run_callbacks = {}
-
971
while record = ite.shift
-
1182
trigger_callbacks = record.trigger_transactional_callbacks?
-
1182
should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
-
1182
already_run_callbacks[record] ||= trigger_callbacks
-
1182
record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
-
end
-
ensure
-
110199
ite&.each do |i|
-
3
i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
-
end
-
end
-
-
3
def before_commit_records
-
18119
records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
-
end
-
-
3
def commit_records
-
18108
return unless records
-
13401
ite = records.uniq(&:__id__)
-
13401
already_run_callbacks = {}
-
13401
while record = ite.shift
-
16273
if @run_commit_callbacks
-
16260
trigger_callbacks = record.trigger_transactional_callbacks?
-
16260
should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
-
16260
already_run_callbacks[record] ||= trigger_callbacks
-
16260
record.committed!(should_run_callbacks: should_run_callbacks)
-
else
-
# if not running callbacks, only adds the record to the parent transaction
-
13
connection.add_transaction_record(record)
-
end
-
end
-
ensure
-
18117
ite&.each { |i| i.committed!(should_run_callbacks: false) }
-
end
-
-
349
def full_rollback?; true; end
-
33166
def joinable?; @joinable; end
-
315351
def closed?; false; end
-
315351
def open?; !closed?; end
-
end
-
-
3
class SavepointTransaction < Transaction
-
3
def initialize(connection, savepoint_name, parent_transaction, **options)
-
12745
super(connection, **options)
-
-
12745
parent_transaction.state.add_child(@state)
-
-
12745
if isolation_level
-
1
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
-
end
-
-
12744
@savepoint_name = savepoint_name
-
end
-
-
3
def materialize!
-
11898
connection.create_savepoint(savepoint_name)
-
11898
super
-
end
-
-
3
def rollback
-
783
connection.rollback_to_savepoint(savepoint_name) if materialized?
-
783
@state.rollback!
-
end
-
-
3
def commit
-
11961
connection.release_savepoint(savepoint_name) if materialized?
-
11961
@state.commit!
-
end
-
-
842
def full_rollback?; false; end
-
end
-
-
3
class RealTransaction < Transaction
-
3
def materialize!
-
115391
if isolation_level
-
20
connection.begin_isolated_db_transaction(isolation_level)
-
else
-
115371
connection.begin_db_transaction
-
end
-
-
115385
super
-
end
-
-
3
def rollback
-
109424
connection.rollback_db_transaction if materialized?
-
109424
@state.full_rollback!
-
end
-
-
3
def commit
-
6164
connection.commit_db_transaction if materialized?
-
6159
@state.full_commit!
-
end
-
end
-
-
3
class TransactionManager #:nodoc:
-
3
def initialize(connection)
-
2013
@stack = []
-
2013
@connection = connection
-
2013
@has_unmaterialized_transactions = false
-
2013
@materializing_transactions = false
-
2013
@lazy_transactions_enabled = true
-
end
-
-
3
def begin_transaction(isolation: nil, joinable: true, _lazy: true)
-
128520
@connection.lock.synchronize do
-
128520
run_commit_callbacks = !current_transaction.joinable?
-
128520
transaction =
-
128520
if @stack.empty?
-
115775
RealTransaction.new(
-
@connection,
-
isolation: isolation,
-
joinable: joinable,
-
run_commit_callbacks: run_commit_callbacks
-
)
-
else
-
12745
SavepointTransaction.new(
-
@connection,
-
"active_record_#{@stack.size}",
-
@stack.last,
-
isolation: isolation,
-
joinable: joinable,
-
run_commit_callbacks: run_commit_callbacks
-
)
-
end
-
-
128519
if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
-
19184
@has_unmaterialized_transactions = true
-
else
-
109335
transaction.materialize!
-
end
-
128519
@stack.push(transaction)
-
128519
transaction
-
end
-
end
-
-
3
def disable_lazy_transactions!
-
34
materialize_transactions
-
34
@lazy_transactions_enabled = false
-
end
-
-
3
def enable_lazy_transactions!
-
112912
@lazy_transactions_enabled = true
-
end
-
-
3
def lazy_transactions_enabled?
-
95347
@lazy_transactions_enabled
-
end
-
-
3
def materialize_transactions
-
320134
return if @materializing_transactions
-
305438
return unless @has_unmaterialized_transactions
-
-
18778
@connection.lock.synchronize do
-
18778
begin
-
18778
@materializing_transactions = true
-
49043
@stack.each { |t| t.materialize! unless t.materialized? }
-
ensure
-
18778
@materializing_transactions = false
-
end
-
18772
@has_unmaterialized_transactions = false
-
end
-
end
-
-
3
def commit_transaction
-
18119
@connection.lock.synchronize do
-
18119
transaction = @stack.last
-
-
18119
begin
-
18119
transaction.before_commit_records
-
ensure
-
18119
@stack.pop
-
end
-
-
18116
transaction.commit
-
18108
transaction.commit_records
-
end
-
end
-
-
3
def rollback_transaction(transaction = nil)
-
110199
@connection.lock.synchronize do
-
110199
transaction ||= @stack.pop
-
110199
transaction.rollback
-
110199
transaction.rollback_records
-
end
-
end
-
-
3
def within_new_transaction(isolation: nil, joinable: true)
-
19182
@connection.lock.synchronize do
-
19182
transaction = begin_transaction(isolation: isolation, joinable: joinable)
-
19181
ret = yield
-
18107
completed = true
-
18107
ret
-
rescue Exception => error
-
1060
if transaction
-
1059
rollback_transaction
-
1046
after_failure_actions(transaction, error)
-
end
-
1047
raise
-
ensure
-
19182
if !error && transaction
-
18122
if Thread.current.status == "aborting"
-
3
rollback_transaction
-
else
-
18119
if !completed && transaction.written
-
8
ActiveSupport::Deprecation.warn(<<~EOW)
-
Using `return`, `break` or `throw` to exit a transaction block is
-
deprecated without replacement. If the `throw` came from
-
`Timeout.timeout(duration)`, pass an exception class as a second
-
argument so it doesn't use `throw` to abort its block. This results
-
in the transaction being committed, but in the next release of Rails
-
it will rollback.
-
EOW
-
end
-
18119
begin
-
18119
commit_transaction
-
rescue Exception
-
20
rollback_transaction(transaction) unless transaction.state.completed?
-
20
raise
-
end
-
end
-
end
-
end
-
end
-
-
3
def open_transactions
-
17
@stack.size
-
end
-
-
3
def current_transaction
-
617089
@stack.last || NULL_TRANSACTION
-
end
-
-
3
private
-
3
NULL_TRANSACTION = NullTransaction.new
-
-
# Deallocate invalidated prepared statements outside of the transaction
-
3
def after_failure_actions(transaction, error)
-
1046
return unless transaction.is_a?(RealTransaction)
-
277
return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired)
-
2
@connection.clear_cache!
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "set"
-
3
require "active_record/connection_adapters/schema_cache"
-
3
require "active_record/connection_adapters/sql_type_metadata"
-
3
require "active_record/connection_adapters/abstract/schema_dumper"
-
3
require "active_record/connection_adapters/abstract/schema_creation"
-
3
require "active_support/concurrency/load_interlock_aware_monitor"
-
3
require "arel/collectors/bind"
-
3
require "arel/collectors/composite"
-
3
require "arel/collectors/sql_string"
-
3
require "arel/collectors/substitute_binds"
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
# Active Record supports multiple database systems. AbstractAdapter and
-
# related classes form the abstraction layer which makes this possible.
-
# An AbstractAdapter represents a connection to a database, and provides an
-
# abstract interface for database-specific functionality such as establishing
-
# a connection, escaping values, building the right SQL fragments for +:offset+
-
# and +:limit+ options, etc.
-
#
-
# All the concrete database adapters follow the interface laid down in this class.
-
# {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
-
# you can use.
-
#
-
# Most of the methods in the adapter are useful during migrations. Most
-
# notably, the instance methods provided by SchemaStatements are very useful.
-
3
class AbstractAdapter
-
3
ADAPTER_NAME = "Abstract"
-
3
include ActiveSupport::Callbacks
-
3
define_callbacks :checkout, :checkin
-
-
3
include Quoting, DatabaseStatements, SchemaStatements
-
3
include DatabaseLimits
-
3
include QueryCache
-
3
include Savepoints
-
-
3
SIMPLE_INT = /\A\d+\z/
-
3
COMMENT_REGEX = %r{/\*(?:[^\*]|\*[^/])*\*/}m
-
-
3
attr_accessor :pool
-
3
attr_reader :visitor, :owner, :logger, :lock
-
3
alias :in_use? :owner
-
-
3
set_callback :checkin, :after, :enable_lazy_transactions!
-
-
3
def self.type_cast_config_to_integer(config)
-
1336
if config.is_a?(Integer)
-
325
config
-
1011
elsif SIMPLE_INT.match?(config)
-
config.to_i
-
else
-
1011
config
-
end
-
end
-
-
3
def self.type_cast_config_to_boolean(config)
-
2108
if config == "false"
-
false
-
else
-
2108
config
-
end
-
end
-
-
3
DEFAULT_READ_QUERY = [:begin, :commit, :explain, :release, :rollback, :savepoint, :select, :with] # :nodoc:
-
3
private_constant :DEFAULT_READ_QUERY
-
-
3
def self.build_read_query_regexp(*parts) # :nodoc:
-
5
parts += DEFAULT_READ_QUERY
-
60
parts = parts.map { |part| /#{part}/i }
-
5
/\A(?:[\(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
-
end
-
-
3
def self.quoted_column_names # :nodoc:
-
675704
@quoted_column_names ||= {}
-
end
-
-
3
def self.quoted_table_names # :nodoc:
-
684746
@quoted_table_names ||= {}
-
end
-
-
3
def initialize(connection, logger = nil, config = {}) # :nodoc:
-
1052
super()
-
-
1052
@connection = connection
-
1052
@owner = nil
-
1052
@instrumenter = ActiveSupport::Notifications.instrumenter
-
1052
@logger = logger
-
1052
@config = config
-
1052
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
-
1052
@idle_since = Concurrent.monotonic_time
-
1052
@visitor = arel_visitor
-
1052
@statements = build_statement_pool
-
1052
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
-
-
1052
@prepared_statements = self.class.type_cast_config_to_boolean(
-
config.fetch(:prepared_statements, true)
-
)
-
-
1052
@advisory_locks_enabled = self.class.type_cast_config_to_boolean(
-
config.fetch(:advisory_locks, true)
-
)
-
end
-
-
3
def replica?
-
291045
@config[:replica] || false
-
end
-
-
3
def use_metadata_table?
-
806
@config.fetch(:use_metadata_table, true)
-
end
-
-
# Determines whether writes are currently being prevents.
-
#
-
# Returns true if the connection is a replica, or if +prevent_writes+
-
# is set to true.
-
3
def preventing_writes?
-
291045
replica? || ActiveRecord::Base.connection_handler.prevent_writes
-
end
-
-
3
def migrations_paths # :nodoc:
-
585
@config[:migrations_paths] || Migrator.migrations_paths
-
end
-
-
3
def migration_context # :nodoc:
-
585
MigrationContext.new(migrations_paths, schema_migration)
-
end
-
-
3
def schema_migration # :nodoc:
-
1173
@schema_migration ||= begin
-
1092
conn = self
-
1092
spec_name = conn.pool.pool_config.connection_specification_name
-
-
1092
return ActiveRecord::SchemaMigration if spec_name == "ActiveRecord::Base"
-
-
3
schema_migration_name = "#{spec_name}::SchemaMigration"
-
-
3
Class.new(ActiveRecord::SchemaMigration) do
-
189
define_singleton_method(:name) { schema_migration_name }
-
75
define_singleton_method(:to_s) { schema_migration_name }
-
-
3
self.connection_specification_name = spec_name
-
end
-
end
-
end
-
-
3
def prepared_statements
-
302431
@prepared_statements && !prepared_statements_disabled_cache.include?(object_id)
-
end
-
-
3
def prepared_statements_disabled_cache # :nodoc:
-
302902
Thread.current[:ar_prepared_statements_disabled_cache] ||= Set.new
-
end
-
-
3
class Version
-
3
include Comparable
-
-
3
attr_reader :full_version_string
-
-
3
def initialize(version_string, full_version_string = nil)
-
504
@version = version_string.split(".").map(&:to_i)
-
504
@full_version_string = full_version_string
-
end
-
-
3
def <=>(version_string)
-
627
@version <=> version_string.split(".").map(&:to_i)
-
end
-
-
3
def to_s
-
36
@version.join(".")
-
end
-
end
-
-
3
def valid_type?(type) # :nodoc:
-
16996
!native_database_types[type].nil?
-
end
-
-
# this method must only be called while holding connection pool's mutex
-
3
def lease
-
112996
if in_use?
-
3
msg = +"Cannot lease connection, "
-
3
if @owner == Thread.current
-
3
msg << "it is already leased by the current thread."
-
else
-
msg << "it is already in use by a different thread: #{@owner}. " \
-
"Current thread: #{Thread.current}."
-
end
-
3
raise ActiveRecordError, msg
-
end
-
-
112993
@owner = Thread.current
-
end
-
-
3
def schema_cache
-
183851
@pool.get_schema_cache(self)
-
end
-
-
3
def schema_cache=(cache)
-
cache.connection = self
-
@pool.set_schema_cache(cache)
-
end
-
-
# this method must only be called while holding connection pool's mutex
-
3
def expire
-
112915
if in_use?
-
112915
if @owner != Thread.current
-
raise ActiveRecordError, "Cannot expire connection, " \
-
"it is owned by a different thread: #{@owner}. " \
-
"Current thread: #{Thread.current}."
-
end
-
-
112915
@idle_since = Concurrent.monotonic_time
-
112915
@owner = nil
-
else
-
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
-
end
-
end
-
-
# this method must only be called while holding connection pool's mutex (and a desire for segfaults)
-
3
def steal! # :nodoc:
-
726
if in_use?
-
726
if @owner != Thread.current
-
131
pool.send :remove_connection_from_thread_cache, self, @owner
-
-
131
@owner = Thread.current
-
end
-
else
-
raise ActiveRecordError, "Cannot steal connection, it is not currently leased."
-
end
-
end
-
-
# Seconds since this connection was returned to the pool
-
3
def seconds_idle # :nodoc:
-
65
return 0 if in_use?
-
65
Concurrent.monotonic_time - @idle_since
-
end
-
-
3
def unprepared_statement
-
486
cache = prepared_statements_disabled_cache.add(object_id) if @prepared_statements
-
486
yield
-
ensure
-
486
cache&.delete(object_id)
-
end
-
-
# Returns the human-readable name of the adapter. Use mixed case - one
-
# can always use downcase if needed.
-
3
def adapter_name
-
865
self.class::ADAPTER_NAME
-
end
-
-
# Does the database for this adapter exist?
-
3
def self.database_exists?(config)
-
raise NotImplementedError
-
end
-
-
# Does this adapter support DDL rollbacks in transactions? That is, would
-
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
-
3
def supports_ddl_transactions?
-
false
-
end
-
-
3
def supports_bulk_alter?
-
66
false
-
end
-
-
# Does this adapter support savepoints?
-
3
def supports_savepoints?
-
false
-
end
-
-
# Does this adapter support application-enforced advisory locking?
-
3
def supports_advisory_locks?
-
214
false
-
end
-
-
# Should primary key values be selected from their corresponding
-
# sequence before the insert statement? If true, next_sequence_value
-
# is called before each insert to set the record's primary key.
-
3
def prefetch_primary_key?(table_name = nil)
-
11108
false
-
end
-
-
3
def supports_partitioned_indexes?
-
2
false
-
end
-
-
# Does this adapter support index sort order?
-
3
def supports_index_sort_order?
-
false
-
end
-
-
# Does this adapter support partial indices?
-
3
def supports_partial_index?
-
false
-
end
-
-
# Does this adapter support expression indices?
-
3
def supports_expression_index?
-
false
-
end
-
-
# Does this adapter support explain?
-
3
def supports_explain?
-
false
-
end
-
-
# Does this adapter support setting the isolation level for a transaction?
-
3
def supports_transaction_isolation?
-
false
-
end
-
-
# Does this adapter support database extensions?
-
3
def supports_extensions?
-
2
false
-
end
-
-
# Does this adapter support creating indexes in the same statement as
-
# creating the table?
-
3
def supports_indexes_in_create?
-
13399
false
-
end
-
-
# Does this adapter support creating foreign key constraints?
-
3
def supports_foreign_keys?
-
false
-
end
-
-
# Does this adapter support creating invalid constraints?
-
3
def supports_validate_constraints?
-
2
false
-
end
-
-
# Does this adapter support creating foreign key constraints
-
# in the same statement as creating the table?
-
3
def supports_foreign_keys_in_create?
-
3
supports_foreign_keys?
-
end
-
3
deprecate :supports_foreign_keys_in_create?
-
-
# Does this adapter support creating check constraints?
-
3
def supports_check_constraints?
-
false
-
end
-
-
# Does this adapter support views?
-
3
def supports_views?
-
false
-
end
-
-
# Does this adapter support materialized views?
-
3
def supports_materialized_views?
-
2
false
-
end
-
-
# Does this adapter support datetime with precision?
-
3
def supports_datetime_with_precision?
-
false
-
end
-
-
# Does this adapter support json data type?
-
3
def supports_json?
-
false
-
end
-
-
# Does this adapter support metadata comments on database objects (tables, columns, indexes)?
-
3
def supports_comments?
-
5198
false
-
end
-
-
# Can comments for tables, columns, and indexes be specified in create/alter table statements?
-
3
def supports_comments_in_create?
-
1506
false
-
end
-
-
# Does this adapter support multi-value insert?
-
3
def supports_multi_insert?
-
3
true
-
end
-
3
deprecate :supports_multi_insert?
-
-
# Does this adapter support virtual columns?
-
3
def supports_virtual_columns?
-
12909
false
-
end
-
-
# Does this adapter support foreign/external tables?
-
3
def supports_foreign_tables?
-
false
-
end
-
-
# Does this adapter support optimizer hints?
-
3
def supports_optimizer_hints?
-
false
-
end
-
-
3
def supports_common_table_expressions?
-
false
-
end
-
-
3
def supports_lazy_transactions?
-
33172
false
-
end
-
-
3
def supports_insert_returning?
-
124
false
-
end
-
-
3
def supports_insert_on_duplicate_skip?
-
false
-
end
-
-
3
def supports_insert_on_duplicate_update?
-
false
-
end
-
-
3
def supports_insert_conflict_target?
-
false
-
end
-
-
# This is meant to be implemented by the adapters that support extensions
-
3
def disable_extension(name)
-
end
-
-
# This is meant to be implemented by the adapters that support extensions
-
3
def enable_extension(name)
-
end
-
-
3
def advisory_locks_enabled? # :nodoc:
-
325
supports_advisory_locks? && @advisory_locks_enabled
-
end
-
-
# This is meant to be implemented by the adapters that support advisory
-
# locks
-
#
-
# Return true if we got the lock, otherwise false
-
3
def get_advisory_lock(lock_id) # :nodoc:
-
end
-
-
# This is meant to be implemented by the adapters that support advisory
-
# locks.
-
#
-
# Return true if we released the lock, otherwise false
-
3
def release_advisory_lock(lock_id) # :nodoc:
-
end
-
-
# A list of extensions, to be filled in by adapters that support them.
-
3
def extensions
-
[]
-
end
-
-
# A list of index algorithms, to be filled by adapters that support them.
-
3
def index_algorithms
-
{}
-
end
-
-
# REFERENTIAL INTEGRITY ====================================
-
-
# Override to turn off referential integrity while executing <tt>&block</tt>.
-
3
def disable_referential_integrity
-
yield
-
end
-
-
# CONNECTION MANAGEMENT ====================================
-
-
# Checks whether the connection to the database is still active. This includes
-
# checking whether the database is actually capable of responding, i.e. whether
-
# the connection isn't stale.
-
3
def active?
-
end
-
-
# Disconnects from the database if already connected, and establishes a
-
# new connection with the database. Implementors should call super if they
-
# override the default implementation.
-
3
def reconnect!
-
266
clear_cache!
-
266
reset_transaction
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
3
def disconnect!
-
630
clear_cache!
-
630
reset_transaction
-
end
-
-
# Immediately forget this connection ever existed. Unlike disconnect!,
-
# this will not communicate with the server.
-
#
-
# After calling this method, the behavior of all other methods becomes
-
# undefined. This is called internally just before a forked process gets
-
# rid of a connection that belonged to its parent.
-
3
def discard!
-
# This should be overridden by concrete adapters.
-
#
-
# Prevent @connection's finalizer from touching the socket, or
-
# otherwise communicating with its server, when it is collected.
-
36
if schema_cache.connection == self
-
36
schema_cache.connection = nil
-
end
-
end
-
-
# Reset the state of this connection, directing the DBMS to clear
-
# transactions and other connection-related server-side state. Usually a
-
# database-dependent operation.
-
#
-
# The default implementation does nothing; the implementation should be
-
# overridden by concrete adapters.
-
3
def reset!
-
# this should be overridden by concrete adapters
-
end
-
-
# Clear any caching the database adapter may be doing.
-
3
def clear_cache!
-
7024
@lock.synchronize { @statements.clear } if @statements
-
end
-
-
# Returns true if its required to reload the connection between requests for development mode.
-
3
def requires_reloading?
-
30
false
-
end
-
-
# Checks whether the connection to the database is still active (i.e. not stale).
-
# This is done under the hood by calling #active?. If the connection
-
# is no longer active, then this method will reconnect to the database.
-
3
def verify!
-
112963
reconnect! unless active?
-
end
-
-
# Provides access to the underlying database driver for this adapter. For
-
# example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
-
# and a PG::Connection object in case of PostgreSQLAdapter.
-
#
-
# This is useful for when you need to call a proprietary method such as
-
# PostgreSQL's lo_* methods.
-
3
def raw_connection
-
34
disable_lazy_transactions!
-
34
@connection
-
end
-
-
3
def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
-
427
attribute.eq(value)
-
end
-
-
3
def case_sensitive_comparison(attribute, value) # :nodoc:
-
33
attribute.eq(value)
-
end
-
-
3
def case_insensitive_comparison(attribute, value) # :nodoc:
-
65
column = column_for_attribute(attribute)
-
-
65
if can_perform_case_insensitive_comparison_for?(column)
-
56
attribute.lower.eq(attribute.relation.lower(value))
-
else
-
9
attribute.eq(value)
-
end
-
end
-
-
3
def can_perform_case_insensitive_comparison_for?(column)
-
38
true
-
end
-
3
private :can_perform_case_insensitive_comparison_for?
-
-
# Check the connection back in to the connection pool
-
3
def close
-
57
pool.checkin self
-
end
-
-
3
def default_index_type?(index) # :nodoc:
-
813
index.using.nil?
-
end
-
-
# Called by ActiveRecord::InsertAll,
-
# Passed an instance of ActiveRecord::InsertAll::Builder,
-
# This method implements standard bulk inserts for all databases, but
-
# should be overridden by adapters to implement common features with
-
# non-standard syntax like handling duplicates or returning values.
-
3
def build_insert_sql(insert) # :nodoc:
-
if insert.skip_duplicates? || insert.update_duplicates?
-
raise NotImplementedError, "#{self.class} should define `build_insert_sql` to implement adapter-specific logic for handling duplicates during INSERT"
-
end
-
-
"INSERT #{insert.into} #{insert.values_list}"
-
end
-
-
3
def get_database_version # :nodoc:
-
end
-
-
3
def database_version # :nodoc:
-
1179
schema_cache.database_version
-
end
-
-
3
def check_version # :nodoc:
-
end
-
-
3
private
-
3
def type_map
-
1289062
@type_map ||= Type::TypeMap.new.tap do |mapping|
-
97
initialize_type_map(mapping)
-
end
-
end
-
-
3
def initialize_type_map(m = type_map)
-
99
register_class_with_limit m, %r(boolean)i, Type::Boolean
-
99
register_class_with_limit m, %r(char)i, Type::String
-
99
register_class_with_limit m, %r(binary)i, Type::Binary
-
99
register_class_with_limit m, %r(text)i, Type::Text
-
99
register_class_with_precision m, %r(date)i, Type::Date
-
99
register_class_with_precision m, %r(time)i, Type::Time
-
99
register_class_with_precision m, %r(datetime)i, Type::DateTime
-
99
register_class_with_limit m, %r(float)i, Type::Float
-
99
register_class_with_limit m, %r(int)i, Type::Integer
-
-
99
m.alias_type %r(blob)i, "binary"
-
99
m.alias_type %r(clob)i, "text"
-
99
m.alias_type %r(timestamp)i, "datetime"
-
99
m.alias_type %r(numeric)i, "decimal"
-
99
m.alias_type %r(number)i, "decimal"
-
99
m.alias_type %r(double)i, "float"
-
-
99
m.register_type %r(^json)i, Type::Json.new
-
-
99
m.register_type(%r(decimal)i) do |sql_type|
-
85
scale = extract_scale(sql_type)
-
85
precision = extract_precision(sql_type)
-
-
85
if scale == 0
-
# FIXME: Remove this class as well
-
34
Type::DecimalWithoutScale.new(precision: precision)
-
else
-
51
Type::Decimal.new(precision: precision, scale: scale)
-
end
-
end
-
end
-
-
3
def reload_type_map
-
226
type_map.clear
-
226
initialize_type_map
-
end
-
-
3
def register_class_with_limit(mapping, key, klass)
-
2575
mapping.register_type(key) do |*args|
-
2911
limit = extract_limit(args.last)
-
2911
klass.new(limit: limit)
-
end
-
end
-
-
3
def register_class_with_precision(mapping, key, klass)
-
1561
mapping.register_type(key) do |*args|
-
1558
precision = extract_precision(args.last)
-
1558
klass.new(precision: precision)
-
end
-
end
-
-
3
def extract_scale(sql_type)
-
435
case sql_type
-
16
when /\((\d+)\)/ then 0
-
177
when /\((\d+)(,(\d+))\)/ then $3.to_i
-
end
-
end
-
-
3
def extract_precision(sql_type)
-
2003
$1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
-
end
-
-
3
def extract_limit(sql_type)
-
2911
$1.to_i if sql_type =~ /\((.*)\)/
-
end
-
-
3
def translate_exception_class(e, sql, binds)
-
2289
message = "#{e.class.name}: #{e.message}"
-
-
2289
exception = translate_exception(
-
e, message: message, sql: sql, binds: binds
-
)
-
2289
exception.set_backtrace e.backtrace
-
2289
exception
-
end
-
-
3
def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
-
@instrumenter.instrument(
-
"sql.active_record",
-
sql: sql,
-
name: name,
-
binds: binds,
-
type_casted_binds: type_casted_binds,
-
statement_name: statement_name,
-
427899
connection: self) do
-
427899
@lock.synchronize do
-
427899
yield
-
end
-
rescue => e
-
2285
raise translate_exception_class(e, sql, binds)
-
end
-
end
-
-
3
def translate_exception(exception, message:, sql:, binds:)
-
# override in derived class
-
2209
case exception
-
when RuntimeError
-
exception
-
else
-
2209
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
-
end
-
end
-
-
3
def without_prepared_statement?(binds)
-
150689
!prepared_statements || binds.empty?
-
end
-
-
3
def column_for(table_name, column_name)
-
53
column_name = column_name.to_s
-
241
columns(table_name).detect { |c| c.name == column_name } ||
-
raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
-
end
-
-
3
def column_for_attribute(attribute)
-
65
table_name = attribute.relation.name
-
65
schema_cache.columns_hash(table_name)[attribute.name.to_s]
-
end
-
-
3
def collector
-
53620
if prepared_statements
-
53122
Arel::Collectors::Composite.new(
-
Arel::Collectors::SQLString.new,
-
Arel::Collectors::Bind.new,
-
)
-
else
-
498
Arel::Collectors::SubstituteBinds.new(
-
self,
-
Arel::Collectors::SQLString.new,
-
)
-
end
-
end
-
-
3
def arel_visitor
-
43
Arel::Visitors::ToSql.new(self)
-
end
-
-
3
def build_statement_pool
-
end
-
-
# Builds the result object.
-
#
-
# This is an internal hook to make possible connection adapters to build
-
# custom result objects with connection-specific data.
-
3
def build_result(columns:, rows:, column_types: {})
-
164288
ActiveRecord::Result.new(columns, rows, column_types)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_record/connection_adapters/abstract_adapter"
-
require "active_record/connection_adapters/statement_pool"
-
require "active_record/connection_adapters/mysql/column"
-
require "active_record/connection_adapters/mysql/explain_pretty_printer"
-
require "active_record/connection_adapters/mysql/quoting"
-
require "active_record/connection_adapters/mysql/schema_creation"
-
require "active_record/connection_adapters/mysql/schema_definitions"
-
require "active_record/connection_adapters/mysql/schema_dumper"
-
require "active_record/connection_adapters/mysql/schema_statements"
-
require "active_record/connection_adapters/mysql/type_metadata"
-
-
module ActiveRecord
-
module ConnectionAdapters
-
class AbstractMysqlAdapter < AbstractAdapter
-
include MySQL::Quoting
-
include MySQL::SchemaStatements
-
-
##
-
# :singleton-method:
-
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
-
# as boolean. If you wish to disable this emulation you can add the following line
-
# to your application.rb file:
-
#
-
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
-
class_attribute :emulate_booleans, default: true
-
-
NATIVE_DATABASE_TYPES = {
-
primary_key: "bigint auto_increment PRIMARY KEY",
-
string: { name: "varchar", limit: 255 },
-
text: { name: "text" },
-
integer: { name: "int", limit: 4 },
-
float: { name: "float", limit: 24 },
-
decimal: { name: "decimal" },
-
datetime: { name: "datetime" },
-
timestamp: { name: "timestamp" },
-
time: { name: "time" },
-
date: { name: "date" },
-
binary: { name: "blob" },
-
blob: { name: "blob" },
-
boolean: { name: "tinyint", limit: 1 },
-
json: { name: "json" },
-
}
-
-
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
-
private
-
def dealloc(stmt)
-
stmt.close
-
end
-
end
-
-
def initialize(connection, logger, connection_options, config)
-
super(connection, logger, config)
-
end
-
-
def get_database_version #:nodoc:
-
full_version_string = get_full_version
-
version_string = version_string(full_version_string)
-
Version.new(version_string, full_version_string)
-
end
-
-
def mariadb? # :nodoc:
-
/mariadb/i.match?(full_version)
-
end
-
-
def supports_bulk_alter?
-
true
-
end
-
-
def supports_index_sort_order?
-
!mariadb? && database_version >= "8.0.1"
-
end
-
-
def supports_expression_index?
-
!mariadb? && database_version >= "8.0.13"
-
end
-
-
def supports_transaction_isolation?
-
true
-
end
-
-
def supports_explain?
-
true
-
end
-
-
def supports_indexes_in_create?
-
true
-
end
-
-
def supports_foreign_keys?
-
true
-
end
-
-
def supports_check_constraints?
-
if mariadb?
-
database_version >= "10.2.1"
-
else
-
database_version >= "8.0.16"
-
end
-
end
-
-
def supports_views?
-
true
-
end
-
-
def supports_datetime_with_precision?
-
mariadb? || database_version >= "5.6.4"
-
end
-
-
def supports_virtual_columns?
-
mariadb? || database_version >= "5.7.5"
-
end
-
-
# See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
-
def supports_optimizer_hints?
-
!mariadb? && database_version >= "5.7.7"
-
end
-
-
def supports_common_table_expressions?
-
if mariadb?
-
database_version >= "10.2.1"
-
else
-
database_version >= "8.0.1"
-
end
-
end
-
-
def supports_advisory_locks?
-
true
-
end
-
-
def supports_insert_on_duplicate_skip?
-
true
-
end
-
-
def supports_insert_on_duplicate_update?
-
true
-
end
-
-
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
-
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
-
end
-
-
def release_advisory_lock(lock_name) # :nodoc:
-
query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
-
end
-
-
def native_database_types
-
NATIVE_DATABASE_TYPES
-
end
-
-
def index_algorithms
-
{
-
default: "ALGORITHM = DEFAULT",
-
copy: "ALGORITHM = COPY",
-
inplace: "ALGORITHM = INPLACE",
-
instant: "ALGORITHM = INSTANT",
-
}
-
end
-
-
# HELPER METHODS ===========================================
-
-
# The two drivers have slightly different ways of yielding hashes of results, so
-
# this method must be implemented to provide a uniform interface.
-
def each_hash(result) # :nodoc:
-
raise NotImplementedError
-
end
-
-
# Must return the MySQL error number from the exception, if the exception has an
-
# error number.
-
def error_number(exception) # :nodoc:
-
raise NotImplementedError
-
end
-
-
# REFERENTIAL INTEGRITY ====================================
-
-
def disable_referential_integrity #:nodoc:
-
old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
-
-
begin
-
update("SET FOREIGN_KEY_CHECKS = 0")
-
yield
-
ensure
-
update("SET FOREIGN_KEY_CHECKS = #{old}")
-
end
-
end
-
-
# CONNECTION MANAGEMENT ====================================
-
-
def clear_cache! # :nodoc:
-
reload_type_map
-
super
-
end
-
-
#--
-
# DATABASE STATEMENTS ======================================
-
#++
-
-
# Executes the SQL statement in the context of this connection.
-
def execute(sql, name = nil)
-
materialize_transactions
-
mark_transaction_written_if_write(sql)
-
-
log(sql, name) do
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
@connection.query(sql)
-
end
-
end
-
end
-
-
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
-
# to write stuff in an abstract way without concerning ourselves about whether it
-
# needs to be explicitly freed or not.
-
def execute_and_free(sql, name = nil) # :nodoc:
-
yield execute(sql, name)
-
end
-
-
def begin_db_transaction
-
execute("BEGIN", "TRANSACTION")
-
end
-
-
def begin_isolated_db_transaction(isolation)
-
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
-
begin_db_transaction
-
end
-
-
def commit_db_transaction #:nodoc:
-
execute("COMMIT", "TRANSACTION")
-
end
-
-
def exec_rollback_db_transaction #:nodoc:
-
execute("ROLLBACK", "TRANSACTION")
-
end
-
-
def empty_insert_statement_value(primary_key = nil)
-
"VALUES ()"
-
end
-
-
# SCHEMA STATEMENTS ========================================
-
-
# Drops the database specified on the +name+ attribute
-
# and creates it again using the provided +options+.
-
def recreate_database(name, options = {})
-
drop_database(name)
-
sql = create_database(name, options)
-
reconnect!
-
sql
-
end
-
-
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
-
# Charset defaults to utf8mb4.
-
#
-
# Example:
-
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
-
# create_database 'matt_development'
-
# create_database 'matt_development', charset: :big5
-
def create_database(name, options = {})
-
if options[:collation]
-
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
-
elsif options[:charset]
-
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
-
elsif row_format_dynamic_by_default?
-
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
-
else
-
raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
-
end
-
end
-
-
# Drops a MySQL database.
-
#
-
# Example:
-
# drop_database('sebastian_development')
-
def drop_database(name) #:nodoc:
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
-
end
-
-
def current_database
-
query_value("SELECT database()", "SCHEMA")
-
end
-
-
# Returns the database character set.
-
def charset
-
show_variable "character_set_database"
-
end
-
-
# Returns the database collation strategy.
-
def collation
-
show_variable "collation_database"
-
end
-
-
def table_comment(table_name) # :nodoc:
-
scope = quoted_scope(table_name)
-
-
query_value(<<~SQL, "SCHEMA").presence
-
SELECT table_comment
-
FROM information_schema.tables
-
WHERE table_schema = #{scope[:schema]}
-
AND table_name = #{scope[:name]}
-
SQL
-
end
-
-
def change_table_comment(table_name, comment_or_changes) # :nodoc:
-
comment = extract_new_comment_value(comment_or_changes)
-
comment = "" if comment.nil?
-
execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
-
end
-
-
# Renames a table.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
def rename_table(table_name, new_name)
-
schema_cache.clear_data_source_cache!(table_name.to_s)
-
schema_cache.clear_data_source_cache!(new_name.to_s)
-
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
-
rename_table_indexes(table_name, new_name)
-
end
-
-
# Drops a table from the database.
-
#
-
# [<tt>:force</tt>]
-
# Set to +:cascade+ to drop dependent objects as well.
-
# Defaults to false.
-
# [<tt>:if_exists</tt>]
-
# Set to +true+ to only drop the table if it exists.
-
# Defaults to false.
-
# [<tt>:temporary</tt>]
-
# Set to +true+ to drop temporary table.
-
# Defaults to false.
-
#
-
# Although this command ignores most +options+ and the block if one is given,
-
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
-
# In that case, +options+ and the block will be used by create_table.
-
def drop_table(table_name, **options)
-
schema_cache.clear_data_source_cache!(table_name.to_s)
-
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
-
end
-
-
def rename_index(table_name, old_name, new_name)
-
if supports_rename_index?
-
validate_index_length!(table_name, new_name)
-
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
-
else
-
super
-
end
-
end
-
-
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
-
default = extract_new_default_value(default_or_changes)
-
change_column table_name, column_name, nil, default: default
-
end
-
-
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
-
unless null || default.nil?
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
-
end
-
-
change_column table_name, column_name, nil, null: null
-
end
-
-
def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
-
comment = extract_new_comment_value(comment_or_changes)
-
change_column table_name, column_name, nil, comment: comment
-
end
-
-
def change_column(table_name, column_name, type, **options) #:nodoc:
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
-
end
-
-
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
-
rename_column_indexes(table_name, column_name, new_column_name)
-
end
-
-
def add_index(table_name, column_name, **options) #:nodoc:
-
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
-
-
return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
-
-
create_index = CreateIndexDefinition.new(index, algorithm)
-
execute schema_creation.accept(create_index)
-
end
-
-
def add_sql_comment!(sql, comment) # :nodoc:
-
sql << " COMMENT #{quote(comment)}" if comment.present?
-
sql
-
end
-
-
def foreign_keys(table_name)
-
raise ArgumentError unless table_name.present?
-
-
scope = quoted_scope(table_name)
-
-
fk_info = exec_query(<<~SQL, "SCHEMA")
-
SELECT fk.referenced_table_name AS 'to_table',
-
fk.referenced_column_name AS 'primary_key',
-
fk.column_name AS 'column',
-
fk.constraint_name AS 'name',
-
rc.update_rule AS 'on_update',
-
rc.delete_rule AS 'on_delete'
-
FROM information_schema.referential_constraints rc
-
JOIN information_schema.key_column_usage fk
-
USING (constraint_schema, constraint_name)
-
WHERE fk.referenced_column_name IS NOT NULL
-
AND fk.table_schema = #{scope[:schema]}
-
AND fk.table_name = #{scope[:name]}
-
AND rc.constraint_schema = #{scope[:schema]}
-
AND rc.table_name = #{scope[:name]}
-
SQL
-
-
fk_info.map do |row|
-
options = {
-
column: row["column"],
-
name: row["name"],
-
primary_key: row["primary_key"]
-
}
-
-
options[:on_update] = extract_foreign_key_action(row["on_update"])
-
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
-
-
ForeignKeyDefinition.new(table_name, row["to_table"], options)
-
end
-
end
-
-
def check_constraints(table_name)
-
scope = quoted_scope(table_name)
-
-
chk_info = exec_query(<<~SQL, "SCHEMA")
-
SELECT cc.constraint_name AS 'name',
-
cc.check_clause AS 'expression'
-
FROM information_schema.check_constraints cc
-
JOIN information_schema.table_constraints tc
-
USING (constraint_schema, constraint_name)
-
WHERE tc.table_schema = #{scope[:schema]}
-
AND tc.table_name = #{scope[:name]}
-
AND cc.constraint_schema = #{scope[:schema]}
-
SQL
-
-
chk_info.map do |row|
-
options = {
-
name: row["name"]
-
}
-
expression = row["expression"]
-
expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
-
CheckConstraintDefinition.new(table_name, expression, options)
-
end
-
end
-
-
def table_options(table_name) # :nodoc:
-
create_table_info = create_table_info(table_name)
-
-
# strip create_definitions and partition_options
-
# Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
-
raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
-
-
return if raw_table_options.empty?
-
-
table_options = {}
-
-
if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
-
raw_table_options = $` + $' # before part + after part
-
table_options[:charset] = charset
-
table_options[:collation] = collation if collation
-
end
-
-
# strip AUTO_INCREMENT
-
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
-
-
# strip COMMENT
-
if raw_table_options.sub!(/ COMMENT='.+'/, "")
-
table_options[:comment] = table_comment(table_name)
-
end
-
-
table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
-
table_options
-
end
-
-
# SHOW VARIABLES LIKE 'name'
-
def show_variable(name)
-
query_value("SELECT @@#{name}", "SCHEMA")
-
rescue ActiveRecord::StatementInvalid
-
nil
-
end
-
-
def primary_keys(table_name) # :nodoc:
-
raise ArgumentError unless table_name.present?
-
-
scope = quoted_scope(table_name)
-
-
query_values(<<~SQL, "SCHEMA")
-
SELECT column_name
-
FROM information_schema.statistics
-
WHERE index_name = 'PRIMARY'
-
AND table_schema = #{scope[:schema]}
-
AND table_name = #{scope[:name]}
-
ORDER BY seq_in_index
-
SQL
-
end
-
-
def default_uniqueness_comparison(attribute, value, klass) # :nodoc:
-
column = column_for_attribute(attribute)
-
-
if column.collation && !column.case_sensitive? && !value.nil?
-
ActiveSupport::Deprecation.warn(<<~MSG.squish)
-
Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
-
To continue case sensitive comparison on the :#{attribute.name} attribute in #{klass} model,
-
pass `case_sensitive: true` option explicitly to the uniqueness validator.
-
MSG
-
attribute.eq(Arel::Nodes::Bin.new(value))
-
else
-
super
-
end
-
end
-
-
def case_sensitive_comparison(attribute, value) # :nodoc:
-
column = column_for_attribute(attribute)
-
-
if column.collation && !column.case_sensitive?
-
attribute.eq(Arel::Nodes::Bin.new(value))
-
else
-
super
-
end
-
end
-
-
def can_perform_case_insensitive_comparison_for?(column)
-
column.case_sensitive?
-
end
-
private :can_perform_case_insensitive_comparison_for?
-
-
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
-
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
-
# distinct queries, and requires that the ORDER BY include the distinct column.
-
# See https://dev.mysql.com/doc/refman/en/group-by-handling.html
-
def columns_for_distinct(columns, orders) # :nodoc:
-
order_columns = orders.compact_blank.map { |s|
-
# Convert Arel node to string
-
s = visitor.compile(s) unless s.is_a?(String)
-
# Remove any ASC/DESC modifiers
-
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
-
}.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
-
-
(order_columns << super).join(", ")
-
end
-
-
def strict_mode?
-
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
-
end
-
-
def default_index_type?(index) # :nodoc:
-
index.using == :btree || super
-
end
-
-
def build_insert_sql(insert) # :nodoc:
-
sql = +"INSERT #{insert.into} #{insert.values_list}"
-
-
if insert.skip_duplicates?
-
no_op_column = quote_column_name(insert.keys.first)
-
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
-
elsif insert.update_duplicates?
-
sql << " ON DUPLICATE KEY UPDATE "
-
sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
-
sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
-
end
-
-
sql
-
end
-
-
def check_version # :nodoc:
-
if database_version < "5.5.8"
-
raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
-
end
-
end
-
-
private
-
def initialize_type_map(m = type_map)
-
super
-
-
m.register_type(%r(char)i) do |sql_type|
-
limit = extract_limit(sql_type)
-
Type.lookup(:string, adapter: :mysql2, limit: limit)
-
end
-
-
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
-
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
-
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
-
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
-
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
-
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
-
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
-
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
-
m.register_type %r(^float)i, Type::Float.new(limit: 24)
-
m.register_type %r(^double)i, Type::Float.new(limit: 53)
-
-
register_integer_type m, %r(^bigint)i, limit: 8
-
register_integer_type m, %r(^int)i, limit: 4
-
register_integer_type m, %r(^mediumint)i, limit: 3
-
register_integer_type m, %r(^smallint)i, limit: 2
-
register_integer_type m, %r(^tinyint)i, limit: 1
-
-
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
-
m.alias_type %r(year)i, "integer"
-
m.alias_type %r(bit)i, "binary"
-
-
m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
-
m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
-
end
-
-
def register_integer_type(mapping, key, **options)
-
mapping.register_type(key) do |sql_type|
-
if /\bunsigned\b/.match?(sql_type)
-
Type::UnsignedInteger.new(**options)
-
else
-
Type::Integer.new(**options)
-
end
-
end
-
end
-
-
def extract_precision(sql_type)
-
if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
-
super || 0
-
else
-
super
-
end
-
end
-
-
# See https://dev.mysql.com/doc/refman/en/server-error-reference.html
-
ER_DB_CREATE_EXISTS = 1007
-
ER_FILSORT_ABORT = 1028
-
ER_DUP_ENTRY = 1062
-
ER_NOT_NULL_VIOLATION = 1048
-
ER_NO_REFERENCED_ROW = 1216
-
ER_ROW_IS_REFERENCED = 1217
-
ER_DO_NOT_HAVE_DEFAULT = 1364
-
ER_ROW_IS_REFERENCED_2 = 1451
-
ER_NO_REFERENCED_ROW_2 = 1452
-
ER_DATA_TOO_LONG = 1406
-
ER_OUT_OF_RANGE = 1264
-
ER_LOCK_DEADLOCK = 1213
-
ER_CANNOT_ADD_FOREIGN = 1215
-
ER_CANNOT_CREATE_TABLE = 1005
-
ER_LOCK_WAIT_TIMEOUT = 1205
-
ER_QUERY_INTERRUPTED = 1317
-
ER_QUERY_TIMEOUT = 3024
-
ER_FK_INCOMPATIBLE_COLUMNS = 3780
-
-
def translate_exception(exception, message:, sql:, binds:)
-
case error_number(exception)
-
when ER_DB_CREATE_EXISTS
-
DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
-
when ER_DUP_ENTRY
-
RecordNotUnique.new(message, sql: sql, binds: binds)
-
when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
-
when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
-
mismatched_foreign_key(message, sql: sql, binds: binds)
-
when ER_CANNOT_CREATE_TABLE
-
if message.include?("errno: 150")
-
mismatched_foreign_key(message, sql: sql, binds: binds)
-
else
-
super
-
end
-
when ER_DATA_TOO_LONG
-
ValueTooLong.new(message, sql: sql, binds: binds)
-
when ER_OUT_OF_RANGE
-
RangeError.new(message, sql: sql, binds: binds)
-
when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
-
NotNullViolation.new(message, sql: sql, binds: binds)
-
when ER_LOCK_DEADLOCK
-
Deadlocked.new(message, sql: sql, binds: binds)
-
when ER_LOCK_WAIT_TIMEOUT
-
LockWaitTimeout.new(message, sql: sql, binds: binds)
-
when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
-
StatementTimeout.new(message, sql: sql, binds: binds)
-
when ER_QUERY_INTERRUPTED
-
QueryCanceled.new(message, sql: sql, binds: binds)
-
else
-
super
-
end
-
end
-
-
def change_column_for_alter(table_name, column_name, type, **options)
-
column = column_for(table_name, column_name)
-
type ||= column.sql_type
-
-
unless options.key?(:default)
-
options[:default] = column.default
-
end
-
-
unless options.key?(:null)
-
options[:null] = column.null
-
end
-
-
unless options.key?(:comment)
-
options[:comment] = column.comment
-
end
-
-
td = create_table_definition(table_name)
-
cd = td.new_column_definition(column.name, type, **options)
-
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
-
end
-
-
def rename_column_for_alter(table_name, column_name, new_column_name)
-
return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
-
-
column = column_for(table_name, column_name)
-
options = {
-
default: column.default,
-
null: column.null,
-
auto_increment: column.auto_increment?,
-
comment: column.comment
-
}
-
-
current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
-
td = create_table_definition(table_name)
-
cd = td.new_column_definition(new_column_name, current_type, **options)
-
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
-
end
-
-
def add_index_for_alter(table_name, column_name, **options)
-
index, algorithm, _ = add_index_options(table_name, column_name, **options)
-
algorithm = ", #{algorithm}" if algorithm
-
-
"ADD #{schema_creation.accept(index)}#{algorithm}"
-
end
-
-
def remove_index_for_alter(table_name, column_name = nil, **options)
-
index_name = index_name_for_remove(table_name, column_name, options)
-
"DROP INDEX #{quote_column_name(index_name)}"
-
end
-
-
def supports_rename_index?
-
if mariadb?
-
database_version >= "10.5.2"
-
else
-
database_version >= "5.7.6"
-
end
-
end
-
-
def supports_rename_column?
-
if mariadb?
-
database_version >= "10.5.2"
-
else
-
database_version >= "8.0.3"
-
end
-
end
-
-
def configure_connection
-
variables = @config.fetch(:variables, {}).stringify_keys
-
-
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
-
variables["sql_auto_is_null"] = 0
-
-
# Increase timeout so the server doesn't disconnect us.
-
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
-
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
-
variables["wait_timeout"] = wait_timeout
-
-
defaults = [":default", :default].to_set
-
-
# Make MySQL reject illegal values rather than truncating or blanking them, see
-
# https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
-
# If the user has provided another value for sql_mode, don't replace it.
-
if sql_mode = variables.delete("sql_mode")
-
sql_mode = quote(sql_mode)
-
elsif !defaults.include?(strict_mode?)
-
if strict_mode?
-
sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
-
else
-
sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
-
sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
-
sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
-
end
-
sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
-
end
-
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
-
-
# NAMES does not have an equals sign, see
-
# https://dev.mysql.com/doc/refman/en/set-names.html
-
# (trailing comma because variable_assignments will always have content)
-
if @config[:encoding]
-
encoding = +"NAMES #{@config[:encoding]}"
-
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
-
encoding << ", "
-
end
-
-
# Gather up all of the SET variables...
-
variable_assignments = variables.map do |k, v|
-
if defaults.include?(v)
-
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
-
elsif !v.nil?
-
"@@SESSION.#{k} = #{quote(v)}"
-
end
-
# or else nil; compact to clear nils out
-
end.compact.join(", ")
-
-
# ...and send them all in one query
-
execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
-
end
-
-
def column_definitions(table_name) # :nodoc:
-
execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
-
each_hash(result)
-
end
-
end
-
-
def create_table_info(table_name) # :nodoc:
-
exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
-
end
-
-
def arel_visitor
-
Arel::Visitors::MySQL.new(self)
-
end
-
-
def build_statement_pool
-
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
-
end
-
-
def mismatched_foreign_key(message, sql:, binds:)
-
match = %r/
-
(?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
-
FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
-
REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
-
/xmi.match(sql)
-
-
options = {
-
message: message,
-
sql: sql,
-
binds: binds,
-
}
-
-
if match
-
options[:table] = match[:table]
-
options[:foreign_key] = match[:foreign_key]
-
options[:target_table] = match[:target_table]
-
options[:primary_key] = match[:primary_key]
-
options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
-
end
-
-
MismatchedForeignKey.new(**options)
-
end
-
-
def version_string(full_version_string)
-
full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
-
end
-
-
# Alias MysqlString to work Mashal.load(File.read("legacy_record.dump")).
-
# TODO: Remove the constant alias once Rails 6.1 has released.
-
MysqlString = Type::String # :nodoc:
-
-
ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
-
Type::ImmutableString.new(true: "1", false: "0", **args)
-
end
-
ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
-
Type::String.new(true: "1", false: "0", **args)
-
end
-
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# :stopdoc:
-
3
module ConnectionAdapters
-
# An abstract definition of a column in a table.
-
3
class Column
-
3
include Deduplicable
-
-
3
attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment
-
-
3
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
-
-
# Instantiates a new column in the table.
-
#
-
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id bigint</tt>.
-
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
-
# +sql_type_metadata+ is various information about the type of the column
-
# +null+ determines if this column allows +NULL+ values.
-
3
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
-
247320
@name = name.freeze
-
247320
@sql_type_metadata = sql_type_metadata
-
247320
@null = null
-
247320
@default = default
-
247320
@default_function = default_function
-
247320
@collation = collation
-
247320
@comment = comment
-
end
-
-
3
def has_default?
-
16929
!default.nil? || default_function
-
end
-
-
3
def bigint?
-
36255
/\Abigint\b/.match?(sql_type)
-
end
-
-
# Returns the human name of the column name.
-
#
-
# ===== Examples
-
# Column.new('sales_stage', ...).human_name # => 'Sales stage'
-
3
def human_name
-
3
Base.human_attribute_name(@name)
-
end
-
-
3
def init_with(coder)
-
4074
@name = coder["name"]
-
4074
@sql_type_metadata = coder["sql_type_metadata"]
-
4074
@null = coder["null"]
-
4074
@default = coder["default"]
-
4074
@default_function = coder["default_function"]
-
4074
@collation = coder["collation"]
-
4074
@comment = coder["comment"]
-
end
-
-
3
def encode_with(coder)
-
3972
coder["name"] = @name
-
3972
coder["sql_type_metadata"] = @sql_type_metadata
-
3972
coder["null"] = @null
-
3972
coder["default"] = @default
-
3972
coder["default_function"] = @default_function
-
3972
coder["collation"] = @collation
-
3972
coder["comment"] = @comment
-
end
-
-
3
def ==(other)
-
258252
other.is_a?(Column) &&
-
name == other.name &&
-
default == other.default &&
-
sql_type_metadata == other.sql_type_metadata &&
-
null == other.null &&
-
default_function == other.default_function &&
-
collation == other.collation &&
-
comment == other.comment
-
end
-
3
alias :eql? :==
-
-
3
def hash
-
Column.hash ^
-
name.hash ^
-
name.encoding.hash ^
-
default.hash ^
-
sql_type_metadata.hash ^
-
null.hash ^
-
default_function.hash ^
-
293128
collation.hash ^
-
comment.hash
-
end
-
-
3
private
-
3
def deduplicated
-
2422
@name = -name
-
2422
@sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
-
2422
@default = -default if default
-
2422
@default_function = -default_function if default_function
-
2422
@collation = -collation if collation
-
2422
@comment = -comment if comment
-
2422
super
-
end
-
end
-
-
3
class NullColumn < Column
-
3
def initialize(name, **)
-
6
super(name, nil)
-
end
-
end
-
end
-
# :startdoc:
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters # :nodoc:
-
3
module Deduplicable
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
3
def registry
-
561685
@registry ||= {}
-
end
-
-
3
def new(*, **)
-
515557
super.deduplicate
-
end
-
end
-
-
3
def deduplicate
-
561685
self.class.registry[self] ||= deduplicated
-
end
-
3
alias :-@ :deduplicate
-
-
3
private
-
3
def deduplicated
-
2976
freeze
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
class Column < ConnectionAdapters::Column # :nodoc:
-
delegate :extra, to: :sql_type_metadata, allow_nil: true
-
-
def unsigned?
-
/\bunsigned(?: zerofill)?\z/.match?(sql_type)
-
end
-
-
def case_sensitive?
-
collation && !collation.end_with?("_ci")
-
end
-
-
def auto_increment?
-
extra == "auto_increment"
-
end
-
-
def virtual?
-
/\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
module DatabaseStatements
-
# Returns an ActiveRecord::Result instance.
-
def select_all(*, **) # :nodoc:
-
result = if ExplainRegistry.collect? && prepared_statements
-
unprepared_statement { super }
-
else
-
super
-
end
-
@connection.abandon_results!
-
result
-
end
-
-
def query(sql, name = nil) # :nodoc:
-
execute(sql, name).to_a
-
end
-
-
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
-
:desc, :describe, :set, :show, :use
-
) # :nodoc:
-
private_constant :READ_QUERY
-
-
def write_query?(sql) # :nodoc:
-
!READ_QUERY.match?(sql)
-
end
-
-
def explain(arel, binds = [])
-
sql = "EXPLAIN #{to_sql(arel, binds)}"
-
start = Concurrent.monotonic_time
-
result = exec_query(sql, "EXPLAIN", binds)
-
elapsed = Concurrent.monotonic_time - start
-
-
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
-
end
-
-
# Executes the SQL statement in the context of this connection.
-
def execute(sql, name = nil)
-
if preventing_writes? && write_query?(sql)
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
-
end
-
-
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
-
# made since we established the connection
-
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
-
-
super
-
end
-
-
def exec_query(sql, name = "SQL", binds = [], prepare: false)
-
if without_prepared_statement?(binds)
-
execute_and_free(sql, name) do |result|
-
if result
-
build_result(columns: result.fields, rows: result.to_a)
-
else
-
build_result(columns: [], rows: [])
-
end
-
end
-
else
-
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
-
if result
-
build_result(columns: result.fields, rows: result.to_a)
-
else
-
build_result(columns: [], rows: [])
-
end
-
end
-
end
-
end
-
-
def exec_delete(sql, name = nil, binds = [])
-
if without_prepared_statement?(binds)
-
@lock.synchronize do
-
execute_and_free(sql, name) { @connection.affected_rows }
-
end
-
else
-
exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
-
end
-
end
-
alias :exec_update :exec_delete
-
-
private
-
def execute_batch(statements, name = nil)
-
combine_multi_statements(statements).each do |statement|
-
execute(statement, name)
-
end
-
@connection.abandon_results!
-
end
-
-
def default_insert_value(column)
-
super unless column.auto_increment?
-
end
-
-
def last_inserted_id(result)
-
@connection.last_id
-
end
-
-
def multi_statements_enabled?
-
flags = @config[:flags]
-
-
if flags.is_a?(Array)
-
flags.include?("MULTI_STATEMENTS")
-
else
-
flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
-
end
-
end
-
-
def with_multi_statements
-
multi_statements_was = multi_statements_enabled?
-
-
unless multi_statements_was
-
@connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
-
end
-
-
yield
-
ensure
-
unless multi_statements_was
-
@connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
-
end
-
end
-
-
def combine_multi_statements(total_sql)
-
total_sql.each_with_object([]) do |sql, total_sql_chunks|
-
previous_packet = total_sql_chunks.last
-
if max_allowed_packet_reached?(sql, previous_packet)
-
total_sql_chunks << +sql
-
else
-
previous_packet << ";\n"
-
previous_packet << sql
-
end
-
end
-
end
-
-
def max_allowed_packet_reached?(current_packet, previous_packet)
-
if current_packet.bytesize > max_allowed_packet
-
raise ActiveRecordError,
-
"Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
-
elsif previous_packet.nil?
-
true
-
else
-
(current_packet.bytesize + previous_packet.bytesize + 2) > max_allowed_packet
-
end
-
end
-
-
def max_allowed_packet
-
@max_allowed_packet ||= show_variable("max_allowed_packet")
-
end
-
-
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
-
if preventing_writes? && write_query?(sql)
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
-
end
-
-
materialize_transactions
-
mark_transaction_written_if_write(sql)
-
-
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
-
# made since we established the connection
-
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
-
-
type_casted_binds = type_casted_binds(binds)
-
-
log(sql, name, binds, type_casted_binds) do
-
if cache_stmt
-
stmt = @statements[sql] ||= @connection.prepare(sql)
-
else
-
stmt = @connection.prepare(sql)
-
end
-
-
begin
-
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
stmt.execute(*type_casted_binds)
-
end
-
rescue Mysql2::Error => e
-
if cache_stmt
-
@statements.delete(sql)
-
else
-
stmt.close
-
end
-
raise e
-
end
-
-
ret = yield stmt, result
-
result.free if result
-
stmt.close unless cache_stmt
-
ret
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
class ExplainPrettyPrinter # :nodoc:
-
# Pretty prints the result of an EXPLAIN in a way that resembles the output of the
-
# MySQL shell:
-
#
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-
# | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-
# | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
-
# | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-
# 2 rows in set (0.00 sec)
-
#
-
# This is an exercise in Ruby hyperrealism :).
-
def pp(result, elapsed)
-
widths = compute_column_widths(result)
-
separator = build_separator(widths)
-
-
pp = []
-
-
pp << separator
-
pp << build_cells(result.columns, widths)
-
pp << separator
-
-
result.rows.each do |row|
-
pp << build_cells(row, widths)
-
end
-
-
pp << separator
-
pp << build_footer(result.rows.length, elapsed)
-
-
pp.join("\n") + "\n"
-
end
-
-
private
-
def compute_column_widths(result)
-
[].tap do |widths|
-
result.columns.each_with_index do |column, i|
-
cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s }
-
widths << cells_in_column.map(&:length).max
-
end
-
end
-
end
-
-
def build_separator(widths)
-
padding = 1
-
"+" + widths.map { |w| "-" * (w + (padding * 2)) }.join("+") + "+"
-
end
-
-
def build_cells(items, widths)
-
cells = []
-
items.each_with_index do |item, i|
-
item = "NULL" if item.nil?
-
justifier = item.is_a?(Numeric) ? "rjust" : "ljust"
-
cells << item.to_s.send(justifier, widths[i])
-
end
-
"| " + cells.join(" | ") + " |"
-
end
-
-
def build_footer(nrows, elapsed)
-
rows_label = nrows == 1 ? "row" : "rows"
-
"#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
module Quoting # :nodoc:
-
def quote_column_name(name)
-
self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
-
end
-
-
def quote_table_name(name)
-
self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
-
end
-
-
def unquoted_true
-
1
-
end
-
-
def unquoted_false
-
0
-
end
-
-
def quoted_date(value)
-
if supports_datetime_with_precision?
-
super
-
else
-
super.sub(/\.\d{6}\z/, "")
-
end
-
end
-
-
def quoted_binary(value)
-
"x'#{value.hex}'"
-
end
-
-
def column_name_matcher
-
COLUMN_NAME
-
end
-
-
def column_name_with_order_matcher
-
COLUMN_NAME_WITH_ORDER
-
end
-
-
COLUMN_NAME = /
-
\A
-
(
-
(?:
-
# `table_name`.`column_name` | function(one or no argument)
-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
-
)
-
(?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
COLUMN_NAME_WITH_ORDER = /
-
\A
-
(
-
(?:
-
# `table_name`.`column_name` | function(one or no argument)
-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
-
)
-
(?:\s+ASC|\s+DESC)?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
-
-
private
-
def _type_cast(value)
-
case value
-
when Date, Time then value
-
else super
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
class SchemaCreation < SchemaCreation # :nodoc:
-
delegate :add_sql_comment!, :mariadb?, to: :@conn, private: true
-
-
private
-
def visit_DropForeignKey(name)
-
"DROP FOREIGN KEY #{name}"
-
end
-
-
def visit_DropCheckConstraint(name)
-
"DROP #{mariadb? ? 'CONSTRAINT' : 'CHECK'} #{name}"
-
end
-
-
def visit_AddColumnDefinition(o)
-
add_column_position!(super, column_options(o.column))
-
end
-
-
def visit_ChangeColumnDefinition(o)
-
change_column_sql = +"CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
-
add_column_position!(change_column_sql, column_options(o.column))
-
end
-
-
def visit_CreateIndexDefinition(o)
-
sql = visit_IndexDefinition(o.index, true)
-
sql << " #{o.algorithm}" if o.algorithm
-
sql
-
end
-
-
def visit_IndexDefinition(o, create = false)
-
index_type = o.type&.to_s&.upcase || o.unique && "UNIQUE"
-
-
sql = create ? ["CREATE"] : []
-
sql << index_type if index_type
-
sql << "INDEX"
-
sql << quote_column_name(o.name)
-
sql << "USING #{o.using}" if o.using
-
sql << "ON #{quote_table_name(o.table)}" if create
-
sql << "(#{quoted_columns(o)})"
-
-
add_sql_comment!(sql.join(" "), o.comment)
-
end
-
-
def add_table_options!(create_sql, o)
-
create_sql = super
-
create_sql << " DEFAULT CHARSET=#{o.charset}" if o.charset
-
create_sql << " COLLATE=#{o.collation}" if o.collation
-
add_sql_comment!(create_sql, o.comment)
-
end
-
-
def add_column_options!(sql, options)
-
# By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
-
# and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
-
# column to contain NULL, explicitly declare it with the NULL attribute.
-
# See https://dev.mysql.com/doc/refman/en/timestamp-initialization.html
-
if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key]
-
sql << " NULL" unless options[:null] == false || options_include_default?(options)
-
end
-
-
if charset = options[:charset]
-
sql << " CHARACTER SET #{charset}"
-
end
-
-
if collation = options[:collation]
-
sql << " COLLATE #{collation}"
-
end
-
-
if as = options[:as]
-
sql << " AS (#{as})"
-
if options[:stored]
-
sql << (mariadb? ? " PERSISTENT" : " STORED")
-
end
-
end
-
-
add_sql_comment!(super, options[:comment])
-
end
-
-
def add_column_position!(sql, options)
-
if options[:first]
-
sql << " FIRST"
-
elsif options[:after]
-
sql << " AFTER #{quote_column_name(options[:after])}"
-
end
-
-
sql
-
end
-
-
def index_in_create(table_name, column_name, options)
-
index, _ = @conn.add_index_options(table_name, column_name, **options)
-
accept(index)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
module ColumnMethods
-
extend ActiveSupport::Concern
-
-
##
-
# :method: blob
-
# :call-seq: blob(*names, **options)
-
-
##
-
# :method: tinyblob
-
# :call-seq: tinyblob(*names, **options)
-
-
##
-
# :method: mediumblob
-
# :call-seq: mediumblob(*names, **options)
-
-
##
-
# :method: longblob
-
# :call-seq: longblob(*names, **options)
-
-
##
-
# :method: tinytext
-
# :call-seq: tinytext(*names, **options)
-
-
##
-
# :method: mediumtext
-
# :call-seq: mediumtext(*names, **options)
-
-
##
-
# :method: longtext
-
# :call-seq: longtext(*names, **options)
-
-
##
-
# :method: unsigned_integer
-
# :call-seq: unsigned_integer(*names, **options)
-
-
##
-
# :method: unsigned_bigint
-
# :call-seq: unsigned_bigint(*names, **options)
-
-
##
-
# :method: unsigned_float
-
# :call-seq: unsigned_float(*names, **options)
-
-
##
-
# :method: unsigned_decimal
-
# :call-seq: unsigned_decimal(*names, **options)
-
-
included do
-
define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
-
:tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
-
:unsigned_float, :unsigned_decimal
-
end
-
end
-
-
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
-
include ColumnMethods
-
-
attr_reader :charset, :collation
-
-
def initialize(conn, name, charset: nil, collation: nil, **)
-
super
-
@charset = charset
-
@collation = collation
-
end
-
-
def new_column_definition(name, type, **options) # :nodoc:
-
case type
-
when :virtual
-
type = options[:type]
-
when :primary_key
-
type = :integer
-
options[:limit] ||= 8
-
options[:primary_key] = true
-
when /\Aunsigned_(?<type>.+)\z/
-
type = $~[:type].to_sym
-
options[:unsigned] = true
-
end
-
-
super
-
end
-
-
private
-
def aliased_types(name, fallback)
-
fallback
-
end
-
-
def integer_like_primary_key_type(type, options)
-
options[:auto_increment] = true
-
type
-
end
-
end
-
-
class Table < ActiveRecord::ConnectionAdapters::Table
-
include ColumnMethods
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
-
private
-
def prepare_column_options(column)
-
spec = super
-
spec[:unsigned] = "true" if column.unsigned?
-
spec[:auto_increment] = "true" if column.auto_increment?
-
-
if /\A(?<size>tiny|medium|long)(?:text|blob)/ =~ column.sql_type
-
spec = { size: size.to_sym.inspect }.merge!(spec)
-
end
-
-
if @connection.supports_virtual_columns? && column.virtual?
-
spec[:as] = extract_expression_for_virtual_column(column)
-
spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
-
spec = { type: schema_type(column).inspect }.merge!(spec)
-
end
-
-
spec
-
end
-
-
def column_spec_for_primary_key(column)
-
spec = super
-
spec.delete(:auto_increment) if column.type == :integer && column.auto_increment?
-
spec
-
end
-
-
def default_primary_key?(column)
-
super && column.auto_increment? && !column.unsigned?
-
end
-
-
def explicit_primary_key_default?(column)
-
column.type == :integer && !column.auto_increment?
-
end
-
-
def schema_type(column)
-
case column.sql_type
-
when /\Atimestamp\b/
-
:timestamp
-
when /\A(?:enum|set)\b/
-
column.sql_type
-
else
-
super
-
end
-
end
-
-
def schema_limit(column)
-
super unless /\A(?:tiny|medium|long)?(?:text|blob)\b/.match?(column.sql_type)
-
end
-
-
def schema_precision(column)
-
super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
-
end
-
-
def schema_collation(column)
-
if column.collation
-
@table_collation_cache ||= {}
-
@table_collation_cache[table_name] ||=
-
@connection.exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"]
-
column.collation.inspect if column.collation != @table_collation_cache[table_name]
-
end
-
end
-
-
def extract_expression_for_virtual_column(column)
-
if @connection.mariadb? && @connection.database_version < "10.2.5"
-
create_table_info = @connection.send(:create_table_info, table_name)
-
column_name = @connection.quote_column_name(column.name)
-
if %r/#{column_name} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info
-
$~[:expression].inspect
-
end
-
else
-
scope = @connection.send(:quoted_scope, table_name)
-
column_name = @connection.quote(column.name)
-
sql = "SELECT generation_expression FROM information_schema.columns" \
-
" WHERE table_schema = #{scope[:schema]}" \
-
" AND table_name = #{scope[:name]}" \
-
" AND column_name = #{column_name}"
-
@connection.query_value(sql, "SCHEMA").inspect
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
module SchemaStatements # :nodoc:
-
# Returns an array of indexes for the given table.
-
def indexes(table_name)
-
indexes = []
-
current_index = nil
-
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
-
each_hash(result) do |row|
-
if current_index != row[:Key_name]
-
next if row[:Key_name] == "PRIMARY" # skip the primary key
-
current_index = row[:Key_name]
-
-
mysql_index_type = row[:Index_type].downcase.to_sym
-
case mysql_index_type
-
when :fulltext, :spatial
-
index_type = mysql_index_type
-
when :btree, :hash
-
index_using = mysql_index_type
-
end
-
-
indexes << [
-
row[:Table],
-
row[:Key_name],
-
row[:Non_unique].to_i == 0,
-
[],
-
lengths: {},
-
orders: {},
-
type: index_type,
-
using: index_using,
-
comment: row[:Index_comment].presence
-
]
-
end
-
-
if row[:Expression]
-
expression = row[:Expression]
-
expression = +"(#{expression})" unless expression.start_with?("(")
-
indexes.last[-2] << expression
-
indexes.last[-1][:expressions] ||= {}
-
indexes.last[-1][:expressions][expression] = expression
-
indexes.last[-1][:orders][expression] = :desc if row[:Collation] == "D"
-
else
-
indexes.last[-2] << row[:Column_name]
-
indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
-
indexes.last[-1][:orders][row[:Column_name]] = :desc if row[:Collation] == "D"
-
end
-
end
-
end
-
-
indexes.map do |index|
-
options = index.pop
-
-
if expressions = options.delete(:expressions)
-
orders = options.delete(:orders)
-
lengths = options.delete(:lengths)
-
-
columns = index[-1].map { |name|
-
[ name.to_sym, expressions[name] || +quote_column_name(name) ]
-
}.to_h
-
-
index[-1] = add_options_for_index_columns(
-
columns, order: orders, length: lengths
-
).values.join(", ")
-
end
-
-
IndexDefinition.new(*index, **options)
-
end
-
end
-
-
def remove_column(table_name, column_name, type = nil, **options)
-
if foreign_key_exists?(table_name, column: column_name)
-
remove_foreign_key(table_name, column: column_name)
-
end
-
super
-
end
-
-
def create_table(table_name, options: default_row_format, **)
-
super
-
end
-
-
def internal_string_options_for_primary_key
-
super.tap do |options|
-
if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
-
options[:collation] = collation.sub(/\A[^_]+/, "utf8")
-
end
-
end
-
end
-
-
def update_table_definition(table_name, base)
-
MySQL::Table.new(table_name, base)
-
end
-
-
def create_schema_dumper(options)
-
MySQL::SchemaDumper.create(self, options)
-
end
-
-
# Maps logical Rails types to MySQL-specific data types.
-
def type_to_sql(type, limit: nil, precision: nil, scale: nil, size: limit_to_size(limit, type), unsigned: nil, **)
-
sql =
-
case type.to_s
-
when "integer"
-
integer_to_sql(limit)
-
when "text"
-
type_with_size_to_sql("text", size)
-
when "blob"
-
type_with_size_to_sql("blob", size)
-
when "binary"
-
if (0..0xfff) === limit
-
"varbinary(#{limit})"
-
else
-
type_with_size_to_sql("blob", size)
-
end
-
else
-
super
-
end
-
-
sql = "#{sql} unsigned" if unsigned && type != :primary_key
-
sql
-
end
-
-
def table_alias_length
-
256 # https://dev.mysql.com/doc/refman/en/identifiers.html
-
end
-
-
private
-
CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
-
-
def row_format_dynamic_by_default?
-
if mariadb?
-
database_version >= "10.2.2"
-
else
-
database_version >= "5.7.9"
-
end
-
end
-
-
def default_row_format
-
return if row_format_dynamic_by_default?
-
-
unless defined?(@default_row_format)
-
if query_value("SELECT @@innodb_file_per_table = 1 AND @@innodb_file_format = 'Barracuda'") == 1
-
@default_row_format = "ROW_FORMAT=DYNAMIC"
-
else
-
@default_row_format = nil
-
end
-
end
-
-
@default_row_format
-
end
-
-
def schema_creation
-
MySQL::SchemaCreation.new(self)
-
end
-
-
def create_table_definition(name, **options)
-
MySQL::TableDefinition.new(self, name, **options)
-
end
-
-
def new_column_from_field(table_name, field)
-
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
-
default, default_function = field[:Default], nil
-
-
if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
-
default, default_function = nil, default
-
elsif type_metadata.extra == "DEFAULT_GENERATED"
-
default = +"(#{default})" unless default.start_with?("(")
-
default, default_function = nil, default
-
end
-
-
MySQL::Column.new(
-
field[:Field],
-
default,
-
type_metadata,
-
field[:Null] == "YES",
-
default_function,
-
collation: field[:Collation],
-
comment: field[:Comment].presence
-
)
-
end
-
-
def fetch_type_metadata(sql_type, extra = "")
-
MySQL::TypeMetadata.new(super(sql_type), extra: extra)
-
end
-
-
def extract_foreign_key_action(specifier)
-
super unless specifier == "RESTRICT"
-
end
-
-
def add_index_length(quoted_columns, **options)
-
lengths = options_for_index_columns(options[:length])
-
quoted_columns.each do |name, column|
-
column << "(#{lengths[name]})" if lengths[name].present?
-
end
-
end
-
-
def add_options_for_index_columns(quoted_columns, **options)
-
quoted_columns = add_index_length(quoted_columns, **options)
-
super
-
end
-
-
def data_source_sql(name = nil, type: nil)
-
scope = quoted_scope(name, type: type)
-
-
sql = +"SELECT table_name FROM (SELECT * FROM information_schema.tables "
-
sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
-
if scope[:type] || scope[:name]
-
conditions = []
-
conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type]
-
conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name]
-
sql << " WHERE #{conditions.join(" AND ")}"
-
end
-
sql
-
end
-
-
def quoted_scope(name = nil, type: nil)
-
schema, name = extract_schema_qualified_name(name)
-
scope = {}
-
scope[:schema] = schema ? quote(schema) : "database()"
-
scope[:name] = quote(name) if name
-
scope[:type] = quote(type) if type
-
scope
-
end
-
-
def extract_schema_qualified_name(string)
-
schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
-
schema, name = nil, schema unless name
-
[schema, name]
-
end
-
-
def type_with_size_to_sql(type, size)
-
case size&.to_s
-
when nil, "tiny", "medium", "long"
-
"#{size}#{type}"
-
else
-
raise ArgumentError,
-
"#{size.inspect} is invalid :size value. Only :tiny, :medium, and :long are allowed."
-
end
-
end
-
-
def limit_to_size(limit, type)
-
case type.to_s
-
when "text", "blob", "binary"
-
case limit
-
when 0..0xff; "tiny"
-
when nil, 0x100..0xffff; nil
-
when 0x10000..0xffffff; "medium"
-
when 0x1000000..0xffffffff; "long"
-
else raise ArgumentError, "No #{type} type has byte size #{limit}"
-
end
-
end
-
end
-
-
def integer_to_sql(limit)
-
case limit
-
when 1; "tinyint"
-
when 2; "smallint"
-
when 3; "mediumint"
-
when nil, 4; "int"
-
when 5..8; "bigint"
-
else raise ArgumentError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead."
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActiveRecord
-
module ConnectionAdapters
-
module MySQL
-
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
-
undef to_yaml if method_defined?(:to_yaml)
-
-
include Deduplicable
-
-
attr_reader :extra
-
-
def initialize(type_metadata, extra: nil)
-
super(type_metadata)
-
@extra = extra
-
end
-
-
def ==(other)
-
other.is_a?(TypeMetadata) &&
-
__getobj__ == other.__getobj__ &&
-
extra == other.extra
-
end
-
alias eql? ==
-
-
def hash
-
TypeMetadata.hash ^
-
__getobj__.hash ^
-
extra.hash
-
end
-
-
private
-
def deduplicated
-
__setobj__(__getobj__.deduplicate)
-
@extra = -extra if extra
-
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_record/connection_adapters/abstract_mysql_adapter"
-
require "active_record/connection_adapters/mysql/database_statements"
-
-
gem "mysql2", "~> 0.5"
-
require "mysql2"
-
-
module ActiveRecord
-
module ConnectionHandling # :nodoc:
-
ER_BAD_DB_ERROR = 1049
-
-
# Establishes a connection to the database that's used by all Active Record objects.
-
def mysql2_connection(config)
-
config = config.symbolize_keys
-
config[:flags] ||= 0
-
-
if config[:flags].kind_of? Array
-
config[:flags].push "FOUND_ROWS"
-
else
-
config[:flags] |= Mysql2::Client::FOUND_ROWS
-
end
-
-
client = Mysql2::Client.new(config)
-
ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
-
rescue Mysql2::Error => error
-
if error.error_number == ER_BAD_DB_ERROR
-
raise ActiveRecord::NoDatabaseError
-
else
-
raise
-
end
-
end
-
end
-
-
module ConnectionAdapters
-
class Mysql2Adapter < AbstractMysqlAdapter
-
ADAPTER_NAME = "Mysql2"
-
-
include MySQL::DatabaseStatements
-
-
def initialize(connection, logger, connection_options, config)
-
superclass_config = config.reverse_merge(prepared_statements: false)
-
super(connection, logger, connection_options, superclass_config)
-
configure_connection
-
end
-
-
def self.database_exists?(config)
-
!!ActiveRecord::Base.mysql2_connection(config)
-
rescue ActiveRecord::NoDatabaseError
-
false
-
end
-
-
def supports_json?
-
!mariadb? && database_version >= "5.7.8"
-
end
-
-
def supports_comments?
-
true
-
end
-
-
def supports_comments_in_create?
-
true
-
end
-
-
def supports_savepoints?
-
true
-
end
-
-
def supports_lazy_transactions?
-
true
-
end
-
-
# HELPER METHODS ===========================================
-
-
def each_hash(result) # :nodoc:
-
if block_given?
-
result.each(as: :hash, symbolize_keys: true) do |row|
-
yield row
-
end
-
else
-
to_enum(:each_hash, result)
-
end
-
end
-
-
def error_number(exception)
-
exception.error_number if exception.respond_to?(:error_number)
-
end
-
-
#--
-
# QUOTING ==================================================
-
#++
-
-
def quote_string(string)
-
@connection.escape(string)
-
end
-
-
#--
-
# CONNECTION MANAGEMENT ====================================
-
#++
-
-
def active?
-
@connection.ping
-
end
-
-
def reconnect!
-
super
-
disconnect!
-
connect
-
end
-
alias :reset! :reconnect!
-
-
# Disconnects from the database if already connected.
-
# Otherwise, this method does nothing.
-
def disconnect!
-
super
-
@connection.close
-
end
-
-
def discard! # :nodoc:
-
super
-
@connection.automatic_close = false
-
@connection = nil
-
end
-
-
private
-
def connect
-
@connection = Mysql2::Client.new(@config)
-
configure_connection
-
end
-
-
def configure_connection
-
@connection.query_options[:as] = :array
-
super
-
end
-
-
def full_version
-
schema_cache.database_version.full_version_string
-
end
-
-
def get_full_version
-
@connection.server_info[:version]
-
end
-
-
def translate_exception(exception, message:, sql:, binds:)
-
if exception.is_a?(Mysql2::Error::TimeoutError) && !exception.error_number
-
ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds)
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
class PoolConfig # :nodoc:
-
3
include Mutex_m
-
-
3
attr_reader :db_config, :connection_specification_name
-
3
attr_accessor :schema_cache
-
-
3
INSTANCES = ObjectSpace::WeakMap.new
-
3
private_constant :INSTANCES
-
-
3
class << self
-
3
def discard_pools!
-
9
INSTANCES.each_key(&:discard_pool!)
-
end
-
end
-
-
3
def initialize(connection_specification_name, db_config)
-
897
super()
-
897
@connection_specification_name = connection_specification_name
-
897
@db_config = db_config
-
897
@pool = nil
-
897
INSTANCES[self] = self
-
end
-
-
3
def disconnect!
-
283
ActiveSupport::ForkTracker.check!
-
-
283
return unless @pool
-
-
283
synchronize do
-
283
return unless @pool
-
-
283
@pool.automatic_reconnect = false
-
283
@pool.disconnect!
-
end
-
-
nil
-
end
-
-
3
def pool
-
1480231
ActiveSupport::ForkTracker.check!
-
-
1481050
@pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
-
end
-
-
3
def discard_pool!
-
129
return unless @pool
-
-
88
synchronize do
-
88
return unless @pool
-
-
88
@pool.discard!
-
88
@pool = nil
-
end
-
end
-
end
-
end
-
end
-
-
3
ActiveSupport::ForkTracker.after_fork { ActiveRecord::ConnectionAdapters::PoolConfig.discard_pools! }
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
class PoolManager # :nodoc:
-
3
def initialize
-
464
@name_to_pool_config = {}
-
end
-
-
3
def pool_configs
-
1187756
@name_to_pool_config.values
-
end
-
-
3
def remove_pool_config(key)
-
393
@name_to_pool_config.delete(key)
-
end
-
-
3
def get_pool_config(key)
-
269778
@name_to_pool_config[key]
-
end
-
-
3
def set_pool_config(key, pool_config)
-
762
@name_to_pool_config[key] = pool_config
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
class Column < ConnectionAdapters::Column # :nodoc:
-
2
delegate :oid, :fmod, to: :sql_type_metadata
-
-
2
def initialize(*, serial: nil, **)
-
20923
super
-
20923
@serial = serial
-
end
-
-
2
def serial?
-
99581
@serial
-
end
-
-
2
def array
-
8040
sql_type_metadata.sql_type.end_with?("[]")
-
end
-
2
alias :array? :array
-
-
2
def sql_type
-
363969
super.delete_suffix("[]")
-
end
-
-
2
def init_with(coder)
-
1707
@serial = coder["serial"]
-
1707
super
-
end
-
-
2
def encode_with(coder)
-
1706
coder["serial"] = @serial
-
1706
super
-
end
-
-
2
def ==(other)
-
24321
other.is_a?(Column) &&
-
super &&
-
serial? == other.serial?
-
end
-
2
alias :eql? :==
-
-
2
def hash
-
Column.hash ^
-
39072
super.hash ^
-
serial?.hash
-
end
-
end
-
end
-
2
PostgreSQLColumn = PostgreSQL::Column # :nodoc:
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module DatabaseStatements
-
2
def explain(arel, binds = [])
-
5
sql = "EXPLAIN #{to_sql(arel, binds)}"
-
5
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
-
end
-
-
# Queries the database and returns the results in an Array-like object
-
2
def query(sql, name = nil) #:nodoc:
-
25860
materialize_transactions
-
25860
mark_transaction_written_if_write(sql)
-
-
25860
log(sql, name) do
-
25860
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
25860
@connection.async_exec(sql).map_types!(@type_map_for_results).values
-
end
-
end
-
end
-
-
2
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
-
:close, :declare, :fetch, :move, :set, :show
-
) # :nodoc:
-
2
private_constant :READ_QUERY
-
-
2
def write_query?(sql) # :nodoc:
-
33791
!READ_QUERY.match?(sql)
-
end
-
-
# Executes an SQL statement, returning a PG::Result object on success
-
# or raising a PG::Error exception otherwise.
-
# Note: the PG::Result object is manually memory managed; if you don't
-
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
-
2
def execute(sql, name = nil)
-
73301
if preventing_writes? && write_query?(sql)
-
3
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
-
end
-
-
73298
materialize_transactions
-
73298
mark_transaction_written_if_write(sql)
-
-
73298
log(sql, name) do
-
73298
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
73298
@connection.async_exec(sql)
-
end
-
end
-
end
-
-
2
def exec_query(sql, name = "SQL", binds = [], prepare: false)
-
22554
execute_and_clear(sql, name, binds, prepare: prepare) do |result|
-
22504
types = {}
-
22504
fields = result.fields
-
22504
fields.each_with_index do |fname, i|
-
129242
ftype = result.ftype i
-
129242
fmod = result.fmod i
-
129242
case type = get_oid_type(ftype, fmod, fname)
-
when Type::Integer, Type::Float, Type::Decimal, Type::String, Type::DateTime, Type::Boolean
-
# skip if a column has already been type casted by pg decoders
-
5915
else types[fname] = type
-
end
-
end
-
22504
build_result(columns: fields, rows: result.values, column_types: types)
-
end
-
end
-
-
2
def exec_delete(sql, name = nil, binds = [])
-
5381
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
-
end
-
2
alias :exec_update :exec_delete
-
-
2
def sql_for_insert(sql, pk, binds) # :nodoc:
-
4340
if pk.nil?
-
# Extract the table from the insert sql. Yuck.
-
14
table_ref = extract_table_ref_from_insert_sql(sql)
-
14
pk = primary_key(table_ref) if table_ref
-
end
-
-
4340
if pk = suppress_composite_primary_key(pk)
-
4067
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
-
end
-
-
4340
super
-
end
-
2
private :sql_for_insert
-
-
2
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
-
4344
if use_insert_returning? || pk == false
-
4340
super
-
else
-
4
result = exec_query(sql, name, binds)
-
4
unless sequence_name
-
3
table_ref = extract_table_ref_from_insert_sql(sql)
-
3
if table_ref
-
3
pk = primary_key(table_ref) if pk.nil?
-
3
pk = suppress_composite_primary_key(pk)
-
3
sequence_name = default_sequence_name(table_ref, pk)
-
end
-
3
return result unless sequence_name
-
end
-
4
last_insert_id_result(sequence_name)
-
end
-
end
-
-
# Begins a transaction.
-
2
def begin_db_transaction
-
26653
execute("BEGIN", "TRANSACTION")
-
end
-
-
2
def begin_isolated_db_transaction(isolation)
-
6
begin_db_transaction
-
6
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
-
end
-
-
# Commits a transaction.
-
2
def commit_db_transaction
-
2788
execute("COMMIT", "TRANSACTION")
-
end
-
-
# Aborts a transaction.
-
2
def exec_rollback_db_transaction
-
23805
execute("ROLLBACK", "TRANSACTION")
-
end
-
-
2
private
-
2
def execute_batch(statements, name = nil)
-
607
execute(combine_multi_statements(statements))
-
end
-
-
2
def build_truncate_statements(table_names)
-
5
["TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}"]
-
end
-
-
# Returns the current ID of a table's sequence.
-
2
def last_insert_id_result(sequence_name)
-
4
exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
-
end
-
-
2
def suppress_composite_primary_key(pk)
-
4343
pk unless pk.is_a?(Array)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
class ExplainPrettyPrinter # :nodoc:
-
# Pretty prints the result of an EXPLAIN in a way that resembles the output of the
-
# PostgreSQL shell:
-
#
-
# QUERY PLAN
-
# ------------------------------------------------------------------------------
-
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
-
# Join Filter: (posts.user_id = users.id)
-
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
-
# Index Cond: (id = 1)
-
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
-
# Filter: (posts.user_id = 1)
-
# (6 rows)
-
#
-
2
def pp(result)
-
5
header = result.columns.first
-
5
lines = result.rows.map(&:first)
-
-
# We add 2 because there's one char of padding at both sides, note
-
# the extra hyphens in the example above.
-
5
width = [header, *lines].map(&:length).max + 2
-
-
5
pp = []
-
-
5
pp << header.center(width).rstrip
-
5
pp << "-" * width
-
-
15
pp += lines.map { |line| " #{line}" }
-
-
5
nrows = result.rows.length
-
5
rows_label = nrows == 1 ? "row" : "rows"
-
5
pp << "(#{nrows} #{rows_label})"
-
-
5
pp.join("\n") + "\n"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_record/connection_adapters/postgresql/oid/array"
-
2
require "active_record/connection_adapters/postgresql/oid/bit"
-
2
require "active_record/connection_adapters/postgresql/oid/bit_varying"
-
2
require "active_record/connection_adapters/postgresql/oid/bytea"
-
2
require "active_record/connection_adapters/postgresql/oid/cidr"
-
2
require "active_record/connection_adapters/postgresql/oid/date"
-
2
require "active_record/connection_adapters/postgresql/oid/date_time"
-
2
require "active_record/connection_adapters/postgresql/oid/decimal"
-
2
require "active_record/connection_adapters/postgresql/oid/enum"
-
2
require "active_record/connection_adapters/postgresql/oid/hstore"
-
2
require "active_record/connection_adapters/postgresql/oid/inet"
-
2
require "active_record/connection_adapters/postgresql/oid/jsonb"
-
2
require "active_record/connection_adapters/postgresql/oid/macaddr"
-
2
require "active_record/connection_adapters/postgresql/oid/money"
-
2
require "active_record/connection_adapters/postgresql/oid/oid"
-
2
require "active_record/connection_adapters/postgresql/oid/point"
-
2
require "active_record/connection_adapters/postgresql/oid/legacy_point"
-
2
require "active_record/connection_adapters/postgresql/oid/range"
-
2
require "active_record/connection_adapters/postgresql/oid/specialized_string"
-
2
require "active_record/connection_adapters/postgresql/oid/uuid"
-
2
require "active_record/connection_adapters/postgresql/oid/vector"
-
2
require "active_record/connection_adapters/postgresql/oid/xml"
-
-
2
require "active_record/connection_adapters/postgresql/oid/type_map_initializer"
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Array < Type::Value # :nodoc:
-
2
include ActiveModel::Type::Helpers::Mutable
-
-
2
Data = Struct.new(:encoder, :values) # :nodoc:
-
-
2
attr_reader :subtype, :delimiter
-
2
delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype
-
-
2
def initialize(subtype, delimiter = ",")
-
916
@subtype = subtype
-
916
@delimiter = delimiter
-
-
916
@pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
-
916
@pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
-
end
-
-
2
def deserialize(value)
-
551
case value
-
when ::String
-
168
type_cast_array(@pg_decoder.decode(value), :deserialize)
-
when Data
-
72
type_cast_array(value.values, :deserialize)
-
else
-
311
super
-
end
-
end
-
-
2
def cast(value)
-
380
if value.is_a?(::String)
-
3
value = begin
-
3
@pg_decoder.decode(value)
-
rescue TypeError
-
# malformed array string is treated as [], will raise in PG 2.0 gem
-
# this keeps a consistent implementation
-
[]
-
end
-
end
-
380
type_cast_array(value, :cast)
-
end
-
-
2
def serialize(value)
-
569
if value.is_a?(::Array)
-
338
casted_values = type_cast_array(value, :serialize)
-
337
Data.new(@pg_encoder, casted_values)
-
else
-
231
super
-
end
-
end
-
-
2
def ==(other)
-
3
other.is_a?(Array) &&
-
subtype == other.subtype &&
-
delimiter == other.delimiter
-
end
-
-
2
def type_cast_for_schema(value)
-
15
return super unless value.is_a?(::Array)
-
39
"[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
-
end
-
-
2
def map(value, &block)
-
3
value.map(&block)
-
end
-
-
2
def changed_in_place?(raw_old_value, new_value)
-
6
deserialize(raw_old_value) != new_value
-
end
-
-
2
def force_equality?(value)
-
1
value.is_a?(::Array)
-
end
-
-
2
private
-
2
def type_cast_array(value, method)
-
1788
if value.is_a?(::Array)
-
1638
value.map { |item| type_cast_array(item, method) }
-
else
-
980
@subtype.public_send(method, value)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Bit < Type::Value # :nodoc:
-
2
def type
-
4
:bit
-
end
-
-
2
def cast_value(value)
-
25
if ::String === value
-
20
case value
-
when /^0x/i
-
1
value[2..-1].hex.to_s(2) # Hexadecimal notation
-
else
-
19
value # Bit-string notation
-
end
-
else
-
5
value.to_s
-
end
-
end
-
-
2
def serialize(value)
-
27
Data.new(super) if value
-
end
-
-
2
class Data
-
2
def initialize(value)
-
23
@value = value
-
end
-
-
2
def to_s
-
23
value
-
end
-
-
2
def binary?
-
13
/\A[01]*\Z/.match?(value)
-
end
-
-
2
def hex?
-
1
/\A[0-9A-F]*\Z/i.match?(value)
-
end
-
-
2
private
-
2
attr_reader :value
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class BitVarying < OID::Bit # :nodoc:
-
2
def type
-
4
:bit_varying
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Bytea < Type::Binary # :nodoc:
-
2
def deserialize(value)
-
55
return if value.nil?
-
24
return value.to_s if value.is_a?(Type::Binary::Data)
-
13
PG::Connection.unescape_bytea(super)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "ipaddr"
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Cidr < Type::Value # :nodoc:
-
2
def type
-
2
:cidr
-
end
-
-
2
def type_cast_for_schema(value)
-
2
subnet_mask = value.instance_variable_get(:@mask_addr)
-
-
# If the subnet mask is equal to /32, don't output it
-
2
if subnet_mask == (2**32 - 1)
-
1
"\"#{value}\""
-
else
-
1
"\"#{value}/#{subnet_mask.to_s(2).count('1')}\""
-
end
-
end
-
-
2
def serialize(value)
-
31
if IPAddr === value
-
12
"#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
-
else
-
19
value
-
end
-
end
-
-
2
def cast_value(value)
-
21
if value.nil?
-
nil
-
21
elsif String === value
-
21
begin
-
21
IPAddr.new(value)
-
2
rescue ArgumentError
-
2
nil
-
end
-
else
-
value
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Date < Type::Date # :nodoc:
-
2
def cast_value(value)
-
1271
case value
-
5
when "infinity" then ::Float::INFINITY
-
2
when "-infinity" then -::Float::INFINITY
-
when / BC$/
-
6
value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
-
3
super(value.delete_suffix!(" BC"))
-
else
-
1261
super
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class DateTime < Type::DateTime # :nodoc:
-
2
def cast_value(value)
-
36440
case value
-
8
when "infinity" then ::Float::INFINITY
-
2
when "-infinity" then -::Float::INFINITY
-
when / BC$/
-
4
value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
-
2
super(value.delete_suffix!(" BC"))
-
else
-
36428
super
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Decimal < Type::Decimal # :nodoc:
-
2
def infinity(options = {})
-
3
BigDecimal("Infinity") * (options[:negative] ? -1 : 1)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Enum < Type::Value # :nodoc:
-
2
def type
-
4
:enum
-
end
-
-
2
private
-
2
def cast_value(value)
-
11
value.to_s
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Hstore < Type::Value # :nodoc:
-
2
include ActiveModel::Type::Helpers::Mutable
-
-
2
def type
-
677
:hstore
-
end
-
-
2
def deserialize(value)
-
393
if value.is_a?(::String)
-
222
::Hash[value.scan(HstorePair).map { |k, v|
-
188
v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
-
188
k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
-
188
[k, v]
-
}]
-
else
-
171
value
-
end
-
end
-
-
2
def serialize(value)
-
404
if value.is_a?(::Hash)
-
410
value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ")
-
209
elsif value.respond_to?(:to_unsafe_h)
-
1
serialize(value.to_unsafe_h)
-
else
-
208
value
-
end
-
end
-
-
2
def accessor
-
22
ActiveRecord::Store::StringKeyedHashAccessor
-
end
-
-
# Will compare the Hash equivalents of +raw_old_value+ and +new_value+.
-
# By comparing hashes, this avoids an edge case where the order of
-
# the keys change between the two hashes, and they would not be marked
-
# as equal.
-
2
def changed_in_place?(raw_old_value, new_value)
-
15
deserialize(raw_old_value) != new_value
-
end
-
-
2
private
-
2
HstorePair = begin
-
2
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
-
2
unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
-
2
/(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
-
end
-
-
2
def escape_hstore(value)
-
430
if value.nil?
-
18
"NULL"
-
else
-
412
if value == ""
-
4
'""'
-
else
-
408
'"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Inet < Cidr # :nodoc:
-
2
def type
-
2
:inet
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Jsonb < Type::Json # :nodoc:
-
2
def type
-
89
:jsonb
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class LegacyPoint < Type::Value # :nodoc:
-
2
include ActiveModel::Type::Helpers::Mutable
-
-
2
def type
-
:point
-
end
-
-
2
def cast(value)
-
67
case value
-
when ::String
-
28
if value.start_with?("(") && value.end_with?(")")
-
28
value = value[1...-1]
-
end
-
28
cast(value.split(","))
-
when ::Array
-
93
value.map { |v| Float(v) }
-
else
-
8
value
-
end
-
end
-
-
2
def serialize(value)
-
34
if value.is_a?(::Array)
-
29
"(#{number_for_point(value[0])},#{number_for_point(value[1])})"
-
else
-
5
super
-
end
-
end
-
-
2
private
-
2
def number_for_point(number)
-
58
number.to_s.delete_suffix(".0")
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Macaddr < Type::String # :nodoc:
-
2
def type
-
2
:macaddr
-
end
-
-
2
def changed?(old_value, new_value, _new_value_before_type_cast)
-
4
old_value.class != new_value.class ||
-
new_value && old_value.casecmp(new_value) != 0
-
end
-
-
2
def changed_in_place?(raw_old_value, new_value)
-
1
raw_old_value.class != new_value.class ||
-
new_value && raw_old_value.casecmp(new_value) != 0
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Money < Type::Decimal # :nodoc:
-
2
def type
-
4
:money
-
end
-
-
2
def scale
-
77
2
-
end
-
-
2
def cast_value(value)
-
53
return value unless ::String === value
-
-
# Because money output is formatted according to the locale, there are two
-
# cases to consider (note the decimal separators):
-
# (1) $12,345,678.12
-
# (2) $12.345.678,12
-
# Negative values are represented as follows:
-
# (3) -$2.55
-
# (4) ($2.55)
-
-
34
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
-
34
case value
-
when /^-?\D*[\d,]+\.\d{2}$/ # (1)
-
32
value.gsub!(/[^-\d.]/, "")
-
when /^-?\D*[\d.]+,\d{2}$/ # (2)
-
2
value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
-
end
-
-
34
super(value)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Oid < Type::UnsignedInteger # :nodoc:
-
2
def type
-
17
:oid
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
Point = Struct.new(:x, :y)
-
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Point < Type::Value # :nodoc:
-
2
include ActiveModel::Type::Helpers::Mutable
-
-
2
def type
-
31
:point
-
end
-
-
2
def cast(value)
-
66
case value
-
when ::String
-
41
return if value.blank?
-
-
40
if value.start_with?("(") && value.end_with?(")")
-
40
value = value[1...-1]
-
end
-
40
x, y = value.split(",")
-
40
build_point(x, y)
-
when ::Array
-
2
build_point(*value)
-
else
-
23
value
-
end
-
end
-
-
2
def serialize(value)
-
124
case value
-
when ActiveRecord::Point
-
63
"(#{number_for_point(value.x)},#{number_for_point(value.y)})"
-
when ::Array
-
28
serialize(build_point(*value))
-
else
-
33
super
-
end
-
end
-
-
2
def type_cast_for_schema(value)
-
8
if ActiveRecord::Point === value
-
8
[value.x, value.y]
-
else
-
super
-
end
-
end
-
-
2
private
-
2
def number_for_point(number)
-
126
number.to_s.delete_suffix(".0")
-
end
-
-
2
def build_point(x, y)
-
70
ActiveRecord::Point.new(Float(x), Float(y))
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Range < Type::Value # :nodoc:
-
2
attr_reader :subtype, :type
-
2
delegate :user_input_in_time_zone, to: :subtype
-
-
2
def initialize(subtype, type = :range)
-
835
@subtype = subtype
-
835
@type = type
-
end
-
-
2
def type_cast_for_schema(value)
-
value.inspect.gsub("Infinity", "::Float::INFINITY")
-
end
-
-
2
def cast_value(value)
-
267
return if value == "empty"
-
252
return value unless value.is_a?(::String)
-
-
210
extracted = extract_bounds(value)
-
210
from = type_cast_single extracted[:from]
-
210
to = type_cast_single extracted[:to]
-
-
210
if !infinity?(from) && extracted[:exclude_start]
-
14
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
-
end
-
196
::Range.new(from, to, extracted[:exclude_end])
-
end
-
-
2
def serialize(value)
-
305
if value.is_a?(::Range)
-
183
from = type_cast_single_for_database(value.begin)
-
183
to = type_cast_single_for_database(value.end)
-
182
::Range.new(from, to, value.exclude_end?)
-
else
-
122
super
-
end
-
end
-
-
2
def ==(other)
-
3
other.is_a?(Range) &&
-
other.subtype == subtype &&
-
other.type == type
-
end
-
-
2
def map(value) # :nodoc:
-
6
new_begin = yield(value.begin)
-
6
new_end = yield(value.end)
-
6
::Range.new(new_begin, new_end, value.exclude_end?)
-
end
-
-
2
def force_equality?(value)
-
3
value.is_a?(::Range)
-
end
-
-
2
private
-
2
def type_cast_single(value)
-
420
infinity?(value) ? value : @subtype.deserialize(value)
-
end
-
-
2
def type_cast_single_for_database(value)
-
366
infinity?(value) ? value : @subtype.serialize(@subtype.cast(value))
-
end
-
-
2
def extract_bounds(value)
-
210
from, to = value[1..-2].split(",", 2)
-
210
{
-
210
from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
-
210
to: (to == "" || to == "infinity") ? infinity : unquote(to),
-
exclude_start: value.start_with?("("),
-
exclude_end: value.end_with?(")")
-
}
-
end
-
-
# When formatting the bound values of range types, PostgreSQL quotes
-
# the bound value using double-quotes in certain conditions. Within
-
# a double-quoted string, literal " and \ characters are themselves
-
# escaped. In input, PostgreSQL accepts multiple escape styles for "
-
# (either \" or "") but in output always uses "".
-
# See:
-
# * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
-
# * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
-
2
def unquote(value)
-
397
if value.start_with?('"') && value.end_with?('"')
-
128
unquoted_value = value[1..-2]
-
128
unquoted_value.gsub!('""', '"')
-
128
unquoted_value.gsub!('\\\\', '\\')
-
128
unquoted_value
-
else
-
269
value
-
end
-
end
-
-
2
def infinity(negative: false)
-
23
if subtype.respond_to?(:infinity)
-
3
subtype.infinity(negative: negative)
-
20
elsif negative
-
8
-::Float::INFINITY
-
else
-
12
::Float::INFINITY
-
end
-
end
-
-
2
def infinity?(value)
-
996
value.respond_to?(:infinite?) && value.infinite?
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class SpecializedString < Type::String # :nodoc:
-
2
attr_reader :type
-
-
2
def initialize(type, **options)
-
5698
@type = type
-
5698
super(**options)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
require "active_support/core_ext/array/extract"
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
# This class uses the data from PostgreSQL pg_type table to build
-
# the OID -> Type mapping.
-
# - OID is an integer representing the type.
-
# - Type is an OID::Type object.
-
# This class has side effects on the +store+ passed during initialization.
-
2
class TypeMapInitializer # :nodoc:
-
2
def initialize(store)
-
726
@store = store
-
end
-
-
2
def run(records)
-
212844
nodes = records.reject { |row| @store.key? row["oid"].to_i }
-
212844
mapped = nodes.extract! { |row| @store.key? row["typname"] }
-
188320
ranges = nodes.extract! { |row| row["typtype"] == "r" }
-
184456
enums = nodes.extract! { |row| row["typtype"] == "e" }
-
184449
domains = nodes.extract! { |row| row["typtype"] == "d" }
-
181288
arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
-
1995
composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
-
-
25250
mapped.each { |row| register_mapped_type(row) }
-
733
enums.each { |row| register_enum_type(row) }
-
3887
domains.each { |row| register_domain_type(row) }
-
180019
arrays.each { |row| register_array_type(row) }
-
4590
ranges.each { |row| register_range_type(row) }
-
1990
composites.each { |row| register_composite_type(row) }
-
end
-
-
2
def query_conditions_for_initial_load
-
25912
known_type_names = @store.keys.map { |n| "'#{n}'" }
-
632
known_type_types = %w('r' 'e' 'd')
-
632
<<~SQL % [known_type_names.join(", "), known_type_types.join(", ")]
-
WHERE
-
t.typname IN (%s)
-
OR t.typtype IN (%s)
-
OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure
-
OR t.typelem != 0
-
SQL
-
end
-
-
2
private
-
2
def register_mapped_type(row)
-
24524
alias_type row["oid"], row["typname"]
-
end
-
-
2
def register_enum_type(row)
-
7
register row["oid"], OID::Enum.new
-
end
-
-
2
def register_array_type(row)
-
179293
register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype|
-
909
OID::Array.new(subtype, row["typdelim"])
-
end
-
end
-
-
2
def register_range_type(row)
-
3864
register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype|
-
830
OID::Range.new(subtype, row["typname"].to_sym)
-
end
-
end
-
-
2
def register_domain_type(row)
-
3161
if base_type = @store.lookup(row["typbasetype"].to_i)
-
3161
register row["oid"], base_type
-
else
-
warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
-
end
-
end
-
-
2
def register_composite_type(row)
-
1264
if subtype = @store.lookup(row["typelem"].to_i)
-
1264
register row["oid"], OID::Vector.new(row["typdelim"], subtype)
-
end
-
end
-
-
2
def register(oid, oid_type = nil, &block)
-
35971
oid = assert_valid_registration(oid, oid_type || block)
-
35971
if block_given?
-
31539
@store.register_type(oid, &block)
-
else
-
4432
@store.register_type(oid, oid_type)
-
end
-
end
-
-
2
def alias_type(oid, target)
-
24524
oid = assert_valid_registration(oid, target)
-
24524
@store.alias_type(oid, target)
-
end
-
-
2
def register_with_subtype(oid, target_oid)
-
183157
if @store.key?(target_oid)
-
31539
register(oid) do |_, *args|
-
1739
yield @store.lookup(target_oid, *args)
-
end
-
end
-
end
-
-
2
def assert_valid_registration(oid, oid_type)
-
60495
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
-
60495
oid.to_i
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Uuid < Type::Value # :nodoc:
-
2
ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
-
-
2
alias :serialize :deserialize
-
-
2
def type
-
182
:uuid
-
end
-
-
2
def changed?(old_value, new_value, _new_value_before_type_cast)
-
13
old_value.class != new_value.class ||
-
new_value && old_value.casecmp(new_value) != 0
-
end
-
-
2
def changed_in_place?(raw_old_value, new_value)
-
13
raw_old_value.class != new_value.class ||
-
new_value && raw_old_value.casecmp(new_value) != 0
-
end
-
-
2
private
-
2
def cast_value(value)
-
188
casted = value.to_s
-
188
casted if casted.match?(ACCEPTABLE_UUID)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Vector < Type::Value # :nodoc:
-
2
attr_reader :delim, :subtype
-
-
# +delim+ corresponds to the `typdelim` column in the pg_types
-
# table. +subtype+ is derived from the `typelem` column in the
-
# pg_types table.
-
2
def initialize(delim, subtype)
-
1264
@delim = delim
-
1264
@subtype = subtype
-
end
-
-
# FIXME: this should probably split on +delim+ and use +subtype+
-
# to cast the values. Unfortunately, the current Rails behavior
-
# is to just return the string.
-
2
def cast(value)
-
value
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module OID # :nodoc:
-
2
class Xml < Type::String # :nodoc:
-
2
def type
-
2
:xml
-
end
-
-
2
def serialize(value)
-
4
return unless value
-
3
Data.new(super)
-
end
-
-
2
class Data # :nodoc:
-
2
def initialize(value)
-
3
@value = value
-
end
-
-
2
def to_s
-
2
@value
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module Quoting
-
# Escapes binary strings for bytea input to the database.
-
2
def escape_bytea(value)
-
78
@connection.escape_bytea(value) if value
-
end
-
-
# Unescapes bytea output from a database to the binary string it represents.
-
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
-
# on escaped binary output from database drive.
-
2
def unescape_bytea(value)
-
@connection.unescape_bytea(value) if value
-
end
-
-
# Quotes strings for use in SQL input.
-
2
def quote_string(s) #:nodoc:
-
52552
@connection.escape(s)
-
end
-
-
# Checks the following cases:
-
#
-
# - table_name
-
# - "table.name"
-
# - schema_name.table_name
-
# - schema_name."table.name"
-
# - "schema.name".table_name
-
# - "schema.name"."table.name"
-
2
def quote_table_name(name) # :nodoc:
-
342680
self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
-
end
-
-
# Quotes schema names for use in SQL queries.
-
2
def quote_schema_name(name)
-
120
PG::Connection.quote_ident(name)
-
end
-
-
2
def quote_table_name_for_assignment(table, attr)
-
quote_column_name(attr)
-
end
-
-
# Quotes column names for use in SQL queries.
-
2
def quote_column_name(name) # :nodoc:
-
91379
self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
-
end
-
-
# Quote date/time values for use in SQL input.
-
2
def quoted_date(value) #:nodoc:
-
22983
if value.year <= 0
-
8
bce_year = format("%04d", -value.year + 1)
-
8
super.sub(/^-?\d+/, bce_year) + " BC"
-
else
-
22975
super
-
end
-
end
-
-
2
def quoted_binary(value) # :nodoc:
-
78
"'#{escape_bytea(value.to_s)}'"
-
end
-
-
2
def quote_default_expression(value, column) # :nodoc:
-
603
if value.is_a?(Proc)
-
18
value.call
-
585
elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value)
-
56
value # Does not quote function default values for UUID columns
-
529
elsif column.respond_to?(:array?)
-
12
type = lookup_cast_type_from_column(column)
-
12
quote(type.serialize(value))
-
else
-
517
super
-
end
-
end
-
-
2
def lookup_cast_type_from_column(column) # :nodoc:
-
347637
type_map.lookup(column.oid, column.fmod, column.sql_type)
-
end
-
-
2
def column_name_matcher
-
690
COLUMN_NAME
-
end
-
-
2
def column_name_with_order_matcher
-
4665
COLUMN_NAME_WITH_ORDER
-
end
-
-
2
COLUMN_NAME = /
-
\A
-
(
-
(?:
-
# "table_name"."column_name"::type_name | function(one or no argument)::type_name
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
-
)
-
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
2
COLUMN_NAME_WITH_ORDER = /
-
\A
-
(
-
(?:
-
# "table_name"."column_name"::type_name | function(one or no argument)::type_name
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
-
)
-
(?:\s+ASC|\s+DESC)?
-
(?:\s+NULLS\s+(?:FIRST|LAST))?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
2
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
-
-
2
private
-
2
def lookup_cast_type(sql_type)
-
517
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
-
end
-
-
2
def _quote(value)
-
561839
case value
-
when OID::Xml::Data
-
"xml '#{quote_string(value.to_s)}'"
-
when OID::Bit::Data
-
13
if value.binary?
-
12
"B'#{value}'"
-
1
elsif value.hex?
-
"X'#{value}'"
-
end
-
when Numeric
-
489015
if value.finite?
-
489009
super
-
else
-
6
"'#{value}'"
-
end
-
when OID::Array::Data
-
94
_quote(encode_array(value))
-
when Range
-
1
_quote(encode_range(value))
-
else
-
72716
super
-
end
-
end
-
-
2
def _type_cast(value)
-
229838
case value
-
when Type::Binary::Data
-
# Return a bind param hash with format as binary.
-
# See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
-
# for more information
-
14
{ value: value.to_s, format: 1 }
-
when OID::Xml::Data, OID::Bit::Data
-
8
value.to_s
-
when OID::Array::Data
-
75
encode_array(value)
-
when Range
-
39
encode_range(value)
-
else
-
229702
super
-
end
-
end
-
-
2
def encode_array(array_data)
-
169
encoder = array_data.encoder
-
169
values = type_cast_array(array_data.values)
-
-
169
result = encoder.encode(values)
-
169
if encoding = determine_encoding_of_strings_in_array(values)
-
56
result.force_encoding(encoding)
-
end
-
169
result
-
end
-
-
2
def encode_range(range)
-
40
"[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}"
-
end
-
-
2
def determine_encoding_of_strings_in_array(value)
-
354
case value
-
185
when ::Array then determine_encoding_of_strings_in_array(value.first)
-
56
when ::String then value.encoding
-
end
-
end
-
-
2
def type_cast_array(values)
-
369
case values
-
409
when ::Array then values.map { |item| type_cast_array(item) }
-
160
else _type_cast(values)
-
end
-
end
-
-
2
def type_cast_range_value(value)
-
80
infinity?(value) ? "" : type_cast(value)
-
end
-
-
2
def infinity?(value)
-
80
value.respond_to?(:infinite?) && value.infinite?
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module ReferentialIntegrity # :nodoc:
-
2
def disable_referential_integrity # :nodoc:
-
619
original_exception = nil
-
-
619
begin
-
619
transaction(requires_new: true) do
-
122399
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
-
end
-
rescue ActiveRecord::ActiveRecordError => e
-
4
original_exception = e
-
end
-
-
618
begin
-
618
yield
-
rescue ActiveRecord::InvalidForeignKey => e
-
1
warn <<-WARNING
-
WARNING: Rails was not able to disable referential integrity.
-
-
This is most likely caused due to missing permissions.
-
Rails needs superuser privileges to disable referential integrity.
-
-
cause: #{original_exception&.message}
-
-
WARNING
-
1
raise e
-
end
-
-
615
begin
-
615
transaction(requires_new: true) do
-
121572
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
-
end
-
2
rescue ActiveRecord::ActiveRecordError
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
class SchemaCreation < SchemaCreation # :nodoc:
-
2
private
-
2
def visit_AlterTable(o)
-
397
super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
-
end
-
-
2
def visit_AddForeignKey(o)
-
90
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
-
end
-
-
2
def visit_ValidateConstraint(name)
-
5
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
-
end
-
-
2
def visit_ChangeColumnDefinition(o)
-
35
column = o.column
-
35
column.sql_type = type_to_sql(column.type, **column.options)
-
35
quoted_column_name = quote_column_name(o.name)
-
-
35
change_column_sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}"
-
-
35
options = column_options(column)
-
-
35
if options[:collation]
-
1
change_column_sql << " COLLATE \"#{options[:collation]}\""
-
end
-
-
35
if options[:using]
-
2
change_column_sql << " USING #{options[:using]}"
-
33
elsif options[:cast_as]
-
2
cast_as_type = type_to_sql(options[:cast_as], **options)
-
2
change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
-
end
-
-
35
if options.key?(:default)
-
15
if options[:default].nil?
-
4
change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
-
else
-
11
quoted_default = quote_default_expression(options[:default], column)
-
11
change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
-
end
-
end
-
-
35
if options.key?(:null)
-
11
change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
-
end
-
-
35
change_column_sql
-
end
-
-
2
def add_column_options!(sql, options)
-
3906
if options[:collation]
-
11
sql << " COLLATE \"#{options[:collation]}\""
-
end
-
3906
super
-
end
-
-
# Returns any SQL string to go between CREATE and TABLE. May be nil.
-
2
def table_modifier_in_create(o)
-
# A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
-
# tables are already UNLOGGED.
-
1513
if o.temporary
-
1
" TEMPORARY"
-
1512
elsif o.unlogged
-
2
" UNLOGGED"
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module ColumnMethods
-
2
extend ActiveSupport::Concern
-
-
# Defines the primary key field.
-
# Use of the native PostgreSQL UUID type is supported, and can be used
-
# by defining your tables as such:
-
#
-
# create_table :stuffs, id: :uuid do |t|
-
# t.string :content
-
# t.timestamps
-
# end
-
#
-
# By default, this will use the <tt>gen_random_uuid()</tt> function from the
-
# +pgcrypto+ extension. As that extension is only available in
-
# PostgreSQL 9.4+, for earlier versions an explicit default can be set
-
# to use <tt>uuid_generate_v4()</tt> from the +uuid-ossp+ extension instead:
-
#
-
# create_table :stuffs, id: false do |t|
-
# t.primary_key :id, :uuid, default: "uuid_generate_v4()"
-
# t.uuid :foo_id
-
# t.timestamps
-
# end
-
#
-
# To enable the appropriate extension, which is a requirement, use
-
# the +enable_extension+ method in your migrations.
-
#
-
# To use a UUID primary key without any of the extensions, set the
-
# +:default+ option to +nil+:
-
#
-
# create_table :stuffs, id: false do |t|
-
# t.primary_key :id, :uuid, default: nil
-
# t.uuid :foo_id
-
# t.timestamps
-
# end
-
#
-
# You may also pass a custom stored procedure that returns a UUID or use a
-
# different UUID generation function from another library.
-
#
-
# Note that setting the UUID primary key default value to +nil+ will
-
# require you to assure that you always provide a UUID value before saving
-
# a record (as primary keys cannot be +nil+). This might be done via the
-
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
-
2
def primary_key(name, type = :primary_key, **options)
-
1385
if type == :uuid
-
41
options[:default] = options.fetch(:default, "gen_random_uuid()")
-
end
-
-
1385
super
-
end
-
-
##
-
# :method: bigserial
-
# :call-seq: bigserial(*names, **options)
-
-
##
-
# :method: bit
-
# :call-seq: bit(*names, **options)
-
-
##
-
# :method: bit_varying
-
# :call-seq: bit_varying(*names, **options)
-
-
##
-
# :method: cidr
-
# :call-seq: cidr(*names, **options)
-
-
##
-
# :method: citext
-
# :call-seq: citext(*names, **options)
-
-
##
-
# :method: daterange
-
# :call-seq: daterange(*names, **options)
-
-
##
-
# :method: hstore
-
# :call-seq: hstore(*names, **options)
-
-
##
-
# :method: inet
-
# :call-seq: inet(*names, **options)
-
-
##
-
# :method: interval
-
# :call-seq: interval(*names, **options)
-
-
##
-
# :method: int4range
-
# :call-seq: int4range(*names, **options)
-
-
##
-
# :method: int8range
-
# :call-seq: int8range(*names, **options)
-
-
##
-
# :method: jsonb
-
# :call-seq: jsonb(*names, **options)
-
-
##
-
# :method: ltree
-
# :call-seq: ltree(*names, **options)
-
-
##
-
# :method: macaddr
-
# :call-seq: macaddr(*names, **options)
-
-
##
-
# :method: money
-
# :call-seq: money(*names, **options)
-
-
##
-
# :method: numrange
-
# :call-seq: numrange(*names, **options)
-
-
##
-
# :method: oid
-
# :call-seq: oid(*names, **options)
-
-
##
-
# :method: point
-
# :call-seq: point(*names, **options)
-
-
##
-
# :method: line
-
# :call-seq: line(*names, **options)
-
-
##
-
# :method: lseg
-
# :call-seq: lseg(*names, **options)
-
-
##
-
# :method: box
-
# :call-seq: box(*names, **options)
-
-
##
-
# :method: path
-
# :call-seq: path(*names, **options)
-
-
##
-
# :method: polygon
-
# :call-seq: polygon(*names, **options)
-
-
##
-
# :method: circle
-
# :call-seq: circle(*names, **options)
-
-
##
-
# :method: serial
-
# :call-seq: serial(*names, **options)
-
-
##
-
# :method: tsrange
-
# :call-seq: tsrange(*names, **options)
-
-
##
-
# :method: tstzrange
-
# :call-seq: tstzrange(*names, **options)
-
-
##
-
# :method: tsvector
-
# :call-seq: tsvector(*names, **options)
-
-
##
-
# :method: uuid
-
# :call-seq: uuid(*names, **options)
-
-
##
-
# :method: xml
-
# :call-seq: xml(*names, **options)
-
-
2
included do
-
4
define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
-
:hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
-
:money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
-
:serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml
-
end
-
end
-
-
2
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
-
2
include ColumnMethods
-
-
2
attr_reader :unlogged
-
-
2
def initialize(*, **)
-
1973
super
-
1973
@unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
-
end
-
-
2
private
-
2
def integer_like_primary_key_type(type, options)
-
17
if type == :bigint || options[:limit] == 8
-
2
:bigserial
-
else
-
15
:serial
-
end
-
end
-
end
-
-
2
class Table < ActiveRecord::ConnectionAdapters::Table
-
2
include ColumnMethods
-
end
-
-
2
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
-
2
attr_reader :constraint_validations
-
-
2
def initialize(td)
-
392
super
-
392
@constraint_validations = []
-
end
-
-
2
def validate_constraint(name)
-
5
@constraint_validations << name
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
-
2
private
-
2
def extensions(stream)
-
109
extensions = @connection.extensions
-
109
if extensions.any?
-
108
stream.puts " # These are extensions that must be enabled in order to support this database"
-
108
extensions.sort.each do |extension|
-
506
stream.puts " enable_extension #{extension.inspect}"
-
end
-
108
stream.puts
-
end
-
end
-
-
2
def prepare_column_options(column)
-
8015
spec = super
-
8015
spec[:array] = "true" if column.array?
-
8015
spec
-
end
-
-
2
def default_primary_key?(column)
-
1915
schema_type(column) == :bigserial
-
end
-
-
2
def explicit_primary_key_default?(column)
-
1915
column.type == :uuid || (column.type == :integer && !column.serial?)
-
end
-
-
2
def schema_type(column)
-
8213
return super unless column.serial?
-
-
1823
if column.bigint?
-
1721
:bigserial
-
else
-
102
:serial
-
end
-
end
-
-
2
def schema_expression(column)
-
1877
super unless column.serial?
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
module SchemaStatements
-
# Drops the database specified on the +name+ attribute
-
# and creates it again using the provided +options+.
-
2
def recreate_database(name, options = {}) #:nodoc:
-
drop_database(name)
-
create_database(name, options)
-
end
-
-
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
-
# <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
-
# <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
-
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
-
#
-
# Example:
-
# create_database config[:database], config
-
# create_database 'foo_development', encoding: 'unicode'
-
2
def create_database(name, options = {})
-
4
options = { encoding: "utf8" }.merge!(options.symbolize_keys)
-
-
4
option_string = options.each_with_object(+"") do |(key, value), memo|
-
6
memo << case key
-
when :owner
-
" OWNER = \"#{value}\""
-
when :template
-
" TEMPLATE = \"#{value}\""
-
when :encoding
-
4
" ENCODING = '#{value}'"
-
when :collation
-
1
" LC_COLLATE = '#{value}'"
-
when :ctype
-
1
" LC_CTYPE = '#{value}'"
-
when :tablespace
-
" TABLESPACE = \"#{value}\""
-
when :connection_limit
-
" CONNECTION LIMIT = #{value}"
-
else
-
""
-
end
-
end
-
-
4
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
-
end
-
-
# Drops a PostgreSQL database.
-
#
-
# Example:
-
# drop_database 'matt_development'
-
2
def drop_database(name) #:nodoc:
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
-
end
-
-
2
def drop_table(table_name, **options) # :nodoc:
-
2529
schema_cache.clear_data_source_cache!(table_name.to_s)
-
2529
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
-
end
-
-
# Returns true if schema exists.
-
2
def schema_exists?(name)
-
4
query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
-
end
-
-
# Verifies existence of an index with a given name.
-
2
def index_name_exists?(table_name, index_name)
-
19
table = quoted_scope(table_name)
-
19
index = quoted_scope(index_name)
-
-
19
query_value(<<~SQL, "SCHEMA").to_i > 0
-
SELECT COUNT(*)
-
FROM pg_class t
-
INNER JOIN pg_index d ON t.oid = d.indrelid
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
-
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
-
WHERE i.relkind IN ('i', 'I')
-
AND i.relname = #{index[:name]}
-
AND t.relname = #{table[:name]}
-
AND n.nspname = #{index[:schema]}
-
SQL
-
end
-
-
# Returns an array of indexes for the given table.
-
2
def indexes(table_name) # :nodoc:
-
3348
scope = quoted_scope(table_name)
-
-
3348
result = query(<<~SQL, "SCHEMA")
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
-
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
-
FROM pg_class t
-
INNER JOIN pg_index d ON t.oid = d.indrelid
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
-
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
-
WHERE i.relkind IN ('i', 'I')
-
AND d.indisprimary = 'f'
-
AND t.relname = #{scope[:name]}
-
AND n.nspname = #{scope[:schema]}
-
ORDER BY i.relname
-
SQL
-
-
3348
result.map do |row|
-
1170
index_name = row[0]
-
1170
unique = row[1]
-
1170
indkey = row[2].split(" ").map(&:to_i)
-
1170
inddef = row[3]
-
1170
oid = row[4]
-
1170
comment = row[5]
-
-
1170
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
-
-
1170
orders = {}
-
1170
opclasses = {}
-
-
1170
if indkey.include?(0)
-
43
columns = expressions
-
else
-
1127
columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact
-
SELECT a.attnum, a.attname
-
FROM pg_attribute a
-
WHERE a.attrelid = #{oid}
-
AND a.attnum IN (#{indkey.join(",")})
-
SQL
-
-
# add info on sort order (only desc order is explicitly specified, asc is the default)
-
# and non-default opclasses
-
1127
expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
-
1397
opclasses[column] = opclass.to_sym if opclass
-
1397
if nulls
-
2
orders[column] = [desc, nulls].compact.join(" ")
-
else
-
1395
orders[column] = :desc if desc
-
end
-
end
-
end
-
-
1170
IndexDefinition.new(
-
table_name,
-
index_name,
-
unique,
-
columns,
-
orders: orders,
-
opclasses: opclasses,
-
where: where,
-
using: using.to_sym,
-
comment: comment.presence
-
)
-
end
-
end
-
-
2
def table_options(table_name) # :nodoc:
-
2152
if comment = table_comment(table_name)
-
2
{ comment: comment }
-
end
-
end
-
-
# Returns a comment stored in database for given table
-
2
def table_comment(table_name) # :nodoc:
-
2159
scope = quoted_scope(table_name, type: "BASE TABLE")
-
2159
if scope[:name]
-
2159
query_value(<<~SQL, "SCHEMA")
-
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
-
FROM pg_catalog.pg_class c
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
-
WHERE c.relname = #{scope[:name]}
-
AND c.relkind IN (#{scope[:type]})
-
AND n.nspname = #{scope[:schema]}
-
SQL
-
end
-
end
-
-
# Returns the current database name.
-
2
def current_database
-
122
query_value("SELECT current_database()", "SCHEMA")
-
end
-
-
# Returns the current schema name.
-
2
def current_schema
-
4
query_value("SELECT current_schema", "SCHEMA")
-
end
-
-
# Returns the current database encoding format.
-
2
def encoding
-
2
query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
-
end
-
-
# Returns the current database collation.
-
2
def collation
-
1
query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
-
end
-
-
# Returns the current database ctype.
-
2
def ctype
-
1
query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
-
end
-
-
# Returns an array of schema names.
-
2
def schema_names
-
6
query_values(<<~SQL, "SCHEMA")
-
SELECT nspname
-
FROM pg_namespace
-
WHERE nspname !~ '^pg_.*'
-
AND nspname NOT IN ('information_schema')
-
ORDER by nspname;
-
SQL
-
end
-
-
# Creates a schema for the given schema name.
-
2
def create_schema(schema_name)
-
9
execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
-
end
-
-
# Drops the schema for the given schema name.
-
2
def drop_schema(schema_name, **options)
-
111
execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
-
end
-
-
# Sets the schema search path to a string of comma-separated schema names.
-
# Names beginning with $ have to be quoted (e.g. $user => '$user').
-
# See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
-
#
-
# This should be not be called manually but set in database.yml.
-
2
def schema_search_path=(schema_csv)
-
770
if schema_csv
-
59
execute("SET search_path TO #{schema_csv}", "SCHEMA")
-
58
@schema_search_path = schema_csv
-
end
-
end
-
-
# Returns the active schema search path.
-
2
def schema_search_path
-
8741
@schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
-
end
-
-
# Returns the current client message level.
-
2
def client_min_messages
-
5
query_value("SHOW client_min_messages", "SCHEMA")
-
end
-
-
# Set the client message level.
-
2
def client_min_messages=(level)
-
718
execute("SET client_min_messages TO '#{level}'", "SCHEMA")
-
end
-
-
# Returns the sequence name for a table's primary key or some other specified key.
-
2
def default_sequence_name(table_name, pk = "id") #:nodoc:
-
87
result = serial_sequence(table_name, pk)
-
81
return nil unless result
-
81
Utils.extract_schema_qualified_name(result).to_s
-
rescue ActiveRecord::StatementInvalid
-
6
PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
-
end
-
-
2
def serial_sequence(table, column)
-
89
query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
-
end
-
-
# Sets the sequence of a table's primary key to the specified value.
-
2
def set_pk_sequence!(table, value) #:nodoc:
-
1
pk, sequence = pk_and_sequence_for(table)
-
-
1
if pk
-
1
if sequence
-
1
quoted_sequence = quote_table_name(sequence)
-
-
1
query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
-
else
-
@logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
-
end
-
end
-
end
-
-
# Resets the sequence of a table's primary key to the maximum value.
-
2
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
-
2476
unless pk && sequence
-
2473
default_pk, default_sequence = pk_and_sequence_for(table)
-
-
2473
pk ||= default_pk
-
2473
sequence ||= default_sequence
-
end
-
-
2476
if @logger && pk && !sequence
-
@logger.warn "#{table} has primary key #{pk} with no default sequence."
-
end
-
-
2476
if pk && sequence
-
2284
quoted_sequence = quote_table_name(sequence)
-
2284
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
-
2284
if max_pk.nil?
-
9
if database_version >= 100000
-
9
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
-
else
-
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
-
end
-
end
-
-
2284
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
-
end
-
end
-
-
# Returns a table's primary key and belonging sequence.
-
2
def pk_and_sequence_for(table) #:nodoc:
-
# First try looking for a sequence with a dependency on the
-
# given table's primary key.
-
2499
result = query(<<~SQL, "SCHEMA")[0]
-
SELECT attr.attname, nsp.nspname, seq.relname
-
FROM pg_class seq,
-
pg_attribute attr,
-
pg_depend dep,
-
pg_constraint cons,
-
pg_namespace nsp
-
WHERE seq.oid = dep.objid
-
AND seq.relkind = 'S'
-
AND attr.attrelid = dep.refobjid
-
AND attr.attnum = dep.refobjsubid
-
AND attr.attrelid = cons.conrelid
-
AND attr.attnum = cons.conkey[1]
-
AND seq.relnamespace = nsp.oid
-
AND cons.contype = 'p'
-
AND dep.classid = 'pg_class'::regclass
-
AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
-
SQL
-
-
2498
if result.nil? || result.empty?
-
199
result = query(<<~SQL, "SCHEMA")[0]
-
SELECT attr.attname, nsp.nspname,
-
CASE
-
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
-
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
-
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
-
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
-
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
-
END
-
FROM pg_class t
-
JOIN pg_attribute attr ON (t.oid = attrelid)
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
-
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
-
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
-
AND cons.contype = 'p'
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
-
SQL
-
end
-
-
2498
pk = result.shift
-
2304
if result.last
-
2301
[pk, PostgreSQL::Name.new(*result)]
-
else
-
3
[pk, nil]
-
end
-
rescue
-
195
nil
-
end
-
-
2
def primary_keys(table_name) # :nodoc:
-
3582
query_values(<<~SQL, "SCHEMA")
-
SELECT a.attname
-
FROM (
-
SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
-
FROM pg_index
-
WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
-
AND indisprimary
-
) i
-
JOIN pg_attribute a
-
ON a.attrelid = i.indrelid
-
AND a.attnum = i.indkey[i.idx]
-
ORDER BY i.idx
-
SQL
-
end
-
-
# Renames a table.
-
# Also renames a table's primary key sequence if the sequence name exists and
-
# matches the Active Record default.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
2
def rename_table(table_name, new_name)
-
14
clear_cache!
-
14
schema_cache.clear_data_source_cache!(table_name.to_s)
-
14
schema_cache.clear_data_source_cache!(new_name.to_s)
-
14
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
-
14
pk, seq = pk_and_sequence_for(new_name)
-
14
if pk
-
14
idx = "#{table_name}_pkey"
-
14
new_idx = "#{new_name}_pkey"
-
14
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
-
14
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
-
12
new_seq = "#{new_name}_#{pk}_seq"
-
12
execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
-
end
-
end
-
14
rename_table_indexes(table_name, new_name)
-
end
-
-
2
def add_column(table_name, column_name, type, **options) #:nodoc:
-
311
clear_cache!
-
311
super
-
305
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
-
end
-
-
2
def change_column(table_name, column_name, type, **options) #:nodoc:
-
33
clear_cache!
-
70
sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) }
-
33
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
-
32
procs.each(&:call)
-
end
-
-
# Changes the default value of a table column.
-
2
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
-
13
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
-
end
-
-
2
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
-
7
clear_cache!
-
7
unless null || default.nil?
-
2
column = column_for(table_name, column_name)
-
2
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
-
end
-
7
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
-
end
-
-
# Adds comment for given table column or drops it if +comment+ is a +nil+
-
2
def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
-
66
clear_cache!
-
66
comment = extract_new_comment_value(comment_or_changes)
-
66
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
-
end
-
-
# Adds comment for given table or drops it if +comment+ is a +nil+
-
2
def change_table_comment(table_name, comment_or_changes) # :nodoc:
-
40
clear_cache!
-
40
comment = extract_new_comment_value(comment_or_changes)
-
40
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
-
end
-
-
# Renames a column in a table.
-
2
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
21
clear_cache!
-
21
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
-
20
rename_column_indexes(table_name, column_name, new_column_name)
-
end
-
-
2
def add_index(table_name, column_name, **options) #:nodoc:
-
503
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
-
-
499
create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
-
499
result = execute schema_creation.accept(create_index)
-
-
499
execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
-
499
result
-
end
-
-
2
def remove_index(table_name, column_name = nil, **options) # :nodoc:
-
55
table = Utils.extract_schema_qualified_name(table_name.to_s)
-
-
55
if options.key?(:name)
-
28
provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
-
-
28
options[:name] = provided_index.identifier
-
28
table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
-
-
28
if provided_index.schema.present? && table.schema != provided_index.schema
-
2
raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
-
end
-
end
-
-
53
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
-
-
52
index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, column_name, options))
-
-
46
execute "DROP INDEX #{index_algorithm(options[:algorithm])} #{quote_table_name(index_to_remove)}"
-
end
-
-
# Renames an index of a table. Raises error if length of new
-
# index name is greater than allowed limit.
-
2
def rename_index(table_name, old_name, new_name)
-
11
validate_index_length!(table_name, new_name)
-
-
10
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
-
end
-
-
2
def foreign_keys(table_name)
-
2263
scope = quoted_scope(table_name)
-
2263
fk_info = exec_query(<<~SQL, "SCHEMA")
-
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
-
FROM pg_constraint c
-
JOIN pg_class t1 ON c.conrelid = t1.oid
-
JOIN pg_class t2 ON c.confrelid = t2.oid
-
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
-
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
-
JOIN pg_namespace t3 ON c.connamespace = t3.oid
-
WHERE c.contype = 'f'
-
AND t1.relname = #{scope[:name]}
-
AND t3.nspname = #{scope[:schema]}
-
ORDER BY c.conname
-
SQL
-
-
2263
fk_info.map do |row|
-
196
options = {
-
column: row["column"],
-
name: row["name"],
-
primary_key: row["primary_key"]
-
}
-
-
196
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
-
196
options[:on_update] = extract_foreign_key_action(row["on_update"])
-
196
options[:validate] = row["valid"]
-
-
196
ForeignKeyDefinition.new(table_name, row["to_table"], options)
-
end
-
end
-
-
2
def foreign_tables
-
1
query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
-
end
-
-
2
def foreign_table_exists?(table_name)
-
5
query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
-
end
-
-
2
def check_constraints(table_name) # :nodoc:
-
2159
scope = quoted_scope(table_name)
-
-
2159
check_info = exec_query(<<-SQL, "SCHEMA")
-
SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef
-
FROM pg_constraint c
-
JOIN pg_class t ON c.conrelid = t.oid
-
WHERE c.contype = 'c'
-
AND t.relname = #{scope[:name]}
-
SQL
-
-
2159
check_info.map do |row|
-
19
options = {
-
name: row["conname"]
-
}
-
19
expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
-
-
19
CheckConstraintDefinition.new(table_name, expression, options)
-
end
-
end
-
-
# Maps logical Rails types to PostgreSQL-specific data types.
-
2
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
-
5266
sql = \
-
case type.to_s
-
when "binary"
-
# PostgreSQL doesn't support limits on binary (bytea) columns.
-
# The hard limit is 1GB, because of a 32-bit size field, and TOAST.
-
39
case limit
-
36
when nil, 0..0x3fffffff; super(type)
-
3
else raise ArgumentError, "No binary type has byte size #{limit}. The limit on binary can be at most 1GB - 1byte."
-
end
-
when "text"
-
# PostgreSQL doesn't support limits on text columns.
-
# The hard limit is 1GB, according to section 8.3 in the manual.
-
72
case limit
-
69
when nil, 0..0x3fffffff; super(type)
-
3
else raise ArgumentError, "No text type has byte size #{limit}. The limit on text can be at most 1GB - 1byte."
-
end
-
when "integer"
-
593
case limit
-
6
when 1, 2; "smallint"
-
573
when nil, 3, 4; "integer"
-
12
when 5..8; "bigint"
-
2
else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
-
end
-
else
-
4562
super
-
end
-
-
5255
sql = "#{sql}[]" if array && type != :primary_key
-
5255
sql
-
end
-
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
-
# requires that the ORDER BY include the distinct column.
-
2
def columns_for_distinct(columns, orders) #:nodoc:
-
95
order_columns = orders.compact_blank.map { |s|
-
# Convert Arel node to string
-
76
s = visitor.compile(s) unless s.is_a?(String)
-
# Remove any ASC/DESC modifiers
-
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
-
76
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
-
76
}.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
-
-
95
(order_columns << super).join(", ")
-
end
-
-
2
def update_table_definition(table_name, base) # :nodoc:
-
119
PostgreSQL::Table.new(table_name, base)
-
end
-
-
2
def create_schema_dumper(options) # :nodoc:
-
109
PostgreSQL::SchemaDumper.create(self, options)
-
end
-
-
# Validates the given constraint.
-
#
-
# Validates the constraint named +constraint_name+ on +accounts+.
-
#
-
# validate_constraint :accounts, :constraint_name
-
2
def validate_constraint(table_name, constraint_name)
-
5
return unless supports_validate_constraints?
-
-
5
at = create_alter_table table_name
-
5
at.validate_constraint constraint_name
-
-
5
execute schema_creation.accept(at)
-
end
-
-
# Validates the given foreign key.
-
#
-
# Validates the foreign key on +accounts.branch_id+.
-
#
-
# validate_foreign_key :accounts, :branches
-
#
-
# Validates the foreign key on +accounts.owner_id+.
-
#
-
# validate_foreign_key :accounts, column: :owner_id
-
#
-
# Validates the foreign key named +special_fk_name+ on the +accounts+ table.
-
#
-
# validate_foreign_key :accounts, name: :special_fk_name
-
#
-
# The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
-
2
def validate_foreign_key(from_table, to_table = nil, **options)
-
5
return unless supports_validate_constraints?
-
-
5
fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name
-
-
4
validate_constraint from_table, fk_name_to_validate
-
end
-
-
2
private
-
2
def schema_creation
-
2466
PostgreSQL::SchemaCreation.new(self)
-
end
-
-
2
def create_table_definition(name, **options)
-
1973
PostgreSQL::TableDefinition.new(self, name, **options)
-
end
-
-
2
def create_alter_table(name)
-
392
PostgreSQL::AlterTable.new create_table_definition(name)
-
end
-
-
2
def new_column_from_field(table_name, field)
-
20923
column_name, type, default, notnull, oid, fmod, collation, comment = field
-
20923
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
-
20923
default_value = extract_value_from_default(default)
-
20923
default_function = extract_default_function(default_value, default)
-
-
20923
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
-
3974
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
-
end
-
-
20923
PostgreSQL::Column.new(
-
column_name,
-
default_value,
-
type_metadata,
-
!notnull,
-
default_function,
-
collation: collation,
-
comment: comment.presence,
-
serial: serial
-
)
-
end
-
-
2
def fetch_type_metadata(column_name, sql_type, oid, fmod)
-
20923
cast_type = get_oid_type(oid, fmod, column_name, sql_type)
-
20923
simple_type = SqlTypeMetadata.new(
-
sql_type: sql_type,
-
type: cast_type.type,
-
limit: cast_type.limit,
-
precision: cast_type.precision,
-
scale: cast_type.scale,
-
)
-
20923
PostgreSQL::TypeMetadata.new(simple_type, oid: oid, fmod: fmod)
-
end
-
-
2
def sequence_name_from_parts(table_name, column_name, suffix)
-
3974
over_length = [table_name, column_name, suffix].sum(&:length) + 2 - max_identifier_length
-
-
3974
if over_length > 0
-
6
column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
-
6
over_length -= column_name.length - column_name_length
-
6
column_name = column_name[0, column_name_length - [over_length, 0].min]
-
end
-
-
3974
if over_length > 0
-
6
table_name = table_name[0, table_name.length - over_length]
-
end
-
-
3974
"#{table_name}_#{column_name}_#{suffix}"
-
end
-
-
2
def extract_foreign_key_action(specifier)
-
392
case specifier
-
16
when "c"; :cascade
-
3
when "n"; :nullify
-
1
when "r"; :restrict
-
end
-
end
-
-
2
def add_column_for_alter(table_name, column_name, type, **options)
-
27
return super unless options.key?(:comment)
-
2
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
-
end
-
-
2
def change_column_for_alter(table_name, column_name, type, **options)
-
35
td = create_table_definition(table_name)
-
35
cd = td.new_column_definition(column_name, type, **options)
-
35
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
-
40
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
-
35
sqls
-
end
-
-
2
def change_column_default_for_alter(table_name, column_name, default_or_changes)
-
13
column = column_for(table_name, column_name)
-
13
return unless column
-
-
13
default = extract_new_default_value(default_or_changes)
-
13
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
-
13
if default.nil?
-
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
-
3
alter_column_query % "DROP DEFAULT"
-
else
-
10
alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
-
end
-
end
-
-
2
def change_column_null_for_alter(table_name, column_name, null, default = nil)
-
7
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
-
end
-
-
2
def add_index_opclass(quoted_columns, **options)
-
488
opclasses = options_for_index_columns(options[:opclass])
-
488
quoted_columns.each do |name, column|
-
548
column << " #{opclasses[name]}" if opclasses[name].present?
-
end
-
end
-
-
2
def add_options_for_index_columns(quoted_columns, **options)
-
488
quoted_columns = add_index_opclass(quoted_columns, **options)
-
488
super
-
end
-
-
2
def data_source_sql(name = nil, type: nil)
-
2219
scope = quoted_scope(name, type: type)
-
2219
scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
-
-
2219
sql = +"SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
-
2219
sql << " WHERE n.nspname = #{scope[:schema]}"
-
2219
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
-
2219
sql << " AND c.relkind IN (#{scope[:type]})"
-
2219
sql
-
end
-
-
2
def quoted_scope(name = nil, type: nil)
-
12186
schema, name = extract_schema_qualified_name(name)
-
12186
type = \
-
case type
-
when "BASE TABLE"
-
3626
"'r','p'"
-
when "VIEW"
-
35
"'v','m'"
-
when "FOREIGN TABLE"
-
5
"'f'"
-
end
-
12186
scope = {}
-
12186
scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
-
12186
scope[:name] = quote(name) if name
-
12186
scope[:type] = type if type
-
12186
scope
-
end
-
-
2
def extract_schema_qualified_name(string)
-
12186
name = Utils.extract_schema_qualified_name(string.to_s)
-
12186
[name.schema, name.identifier]
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
# :stopdoc:
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
2
class TypeMetadata < DelegateClass(SqlTypeMetadata)
-
2
undef to_yaml if method_defined?(:to_yaml)
-
-
2
include Deduplicable
-
-
2
attr_reader :oid, :fmod
-
-
2
def initialize(type_metadata, oid: nil, fmod: nil)
-
20923
super(type_metadata)
-
20923
@oid = oid
-
20923
@fmod = fmod
-
end
-
-
2
def ==(other)
-
44915
other.is_a?(TypeMetadata) &&
-
__getobj__ == other.__getobj__ &&
-
oid == other.oid &&
-
fmod == other.fmod
-
end
-
2
alias eql? ==
-
-
2
def hash
-
TypeMetadata.hash ^
-
__getobj__.hash ^
-
61540
oid.hash ^
-
fmod.hash
-
end
-
-
2
private
-
2
def deduplicated
-
319
__setobj__(__getobj__.deduplicate)
-
319
super
-
end
-
end
-
end
-
2
PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata
-
end
-
end
-
# frozen_string_literal: true
-
-
2
module ActiveRecord
-
2
module ConnectionAdapters
-
2
module PostgreSQL
-
# Value Object to hold a schema qualified name.
-
# This is usually the name of a PostgreSQL relation but it can also represent
-
# schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
-
# double quoting.
-
2
class Name # :nodoc:
-
2
SEPARATOR = "."
-
2
attr_reader :schema, :identifier
-
-
2
def initialize(schema, identifier)
-
15469
@schema, @identifier = unquote(schema), unquote(identifier)
-
end
-
-
2
def to_s
-
258
parts.join SEPARATOR
-
end
-
-
2
def quoted
-
720
if schema
-
135
PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier)
-
else
-
585
PG::Connection.quote_ident(identifier)
-
end
-
end
-
-
2
def ==(o)
-
2233
o.class == self.class && o.parts == parts
-
end
-
2
alias_method :eql?, :==
-
-
2
def hash
-
2445
parts.hash
-
end
-
-
2
protected
-
2
def parts
-
7169
@parts ||= [@schema, @identifier].compact
-
end
-
-
2
private
-
2
def unquote(part)
-
30938
if part && part.start_with?('"')
-
33
part[1..-2]
-
else
-
30905
part
-
end
-
end
-
end
-
-
2
module Utils # :nodoc:
-
2
extend self
-
-
# Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
-
# extracted from +string+.
-
# +schema+ is +nil+ if not specified in +string+.
-
# +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
-
# +string+ supports the range of schema/table references understood by PostgreSQL, for example:
-
#
-
# * <tt>table_name</tt>
-
# * <tt>"table.name"</tt>
-
# * <tt>schema_name.table_name</tt>
-
# * <tt>schema_name."table.name"</tt>
-
# * <tt>"schema_name".table_name</tt>
-
# * <tt>"schema.name"."table name"</tt>
-
2
def extract_schema_qualified_name(string)
-
13063
schema, table = string.scan(/[^".]+|"[^"]*"/)
-
13063
if table.nil?
-
12826
table = schema
-
12826
schema = nil
-
end
-
13063
PostgreSQL::Name.new(schema, table)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
2
gem "pg", "~> 1.1"
-
2
require "pg"
-
-
2
require "active_support/core_ext/object/try"
-
2
require "active_record/connection_adapters/abstract_adapter"
-
2
require "active_record/connection_adapters/statement_pool"
-
2
require "active_record/connection_adapters/postgresql/column"
-
2
require "active_record/connection_adapters/postgresql/database_statements"
-
2
require "active_record/connection_adapters/postgresql/explain_pretty_printer"
-
2
require "active_record/connection_adapters/postgresql/oid"
-
2
require "active_record/connection_adapters/postgresql/quoting"
-
2
require "active_record/connection_adapters/postgresql/referential_integrity"
-
2
require "active_record/connection_adapters/postgresql/schema_creation"
-
2
require "active_record/connection_adapters/postgresql/schema_definitions"
-
2
require "active_record/connection_adapters/postgresql/schema_dumper"
-
2
require "active_record/connection_adapters/postgresql/schema_statements"
-
2
require "active_record/connection_adapters/postgresql/type_metadata"
-
2
require "active_record/connection_adapters/postgresql/utils"
-
-
2
module ActiveRecord
-
2
module ConnectionHandling # :nodoc:
-
# Establishes a connection to the database that's used by all Active Record objects
-
2
def postgresql_connection(config)
-
410
conn_params = config.symbolize_keys.compact
-
-
# Map ActiveRecords param names to PGs.
-
410
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
-
410
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
-
-
# Forward only valid config params to PG::Connection.connect.
-
410
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
-
410
conn_params.slice!(*valid_conn_param_keys)
-
-
410
conn = PG.connect(conn_params)
-
408
ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
-
rescue ::PG::Error => error
-
2
if error.message.include?(conn_params[:dbname])
-
2
raise ActiveRecord::NoDatabaseError
-
else
-
raise
-
end
-
end
-
end
-
-
2
module ConnectionAdapters
-
# The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
-
#
-
# Options:
-
#
-
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
-
# the default is to connect to localhost.
-
# * <tt>:port</tt> - Defaults to 5432.
-
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
-
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
-
# * <tt>:database</tt> - Defaults to be the same as the user name.
-
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
-
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
-
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
-
# <encoding></tt> call on the connection.
-
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
-
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
-
# * <tt>:variables</tt> - An optional hash of additional parameters that
-
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
-
# * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
-
# defaults to true.
-
#
-
# Any further options are used as connection parameters to libpq. See
-
# https://www.postgresql.org/docs/current/static/libpq-connect.html for the
-
# list of parameters.
-
#
-
# In addition, default connection parameters of libpq can be set per environment variables.
-
# See https://www.postgresql.org/docs/current/static/libpq-envars.html .
-
2
class PostgreSQLAdapter < AbstractAdapter
-
2
ADAPTER_NAME = "PostgreSQL"
-
-
##
-
# :singleton-method:
-
# PostgreSQL allows the creation of "unlogged" tables, which do not record
-
# data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
-
# but significantly increases the risk of data loss if the database
-
# crashes. As a result, this should not be used in production
-
# environments. If you would like all created tables to be unlogged in
-
# the test environment you can add the following line to your test.rb
-
# file:
-
#
-
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
-
2
class_attribute :create_unlogged_tables, default: false
-
-
2
NATIVE_DATABASE_TYPES = {
-
primary_key: "bigserial primary key",
-
string: { name: "character varying" },
-
text: { name: "text" },
-
integer: { name: "integer", limit: 4 },
-
float: { name: "float" },
-
decimal: { name: "decimal" },
-
datetime: { name: "timestamp" },
-
time: { name: "time" },
-
date: { name: "date" },
-
daterange: { name: "daterange" },
-
numrange: { name: "numrange" },
-
tsrange: { name: "tsrange" },
-
tstzrange: { name: "tstzrange" },
-
int4range: { name: "int4range" },
-
int8range: { name: "int8range" },
-
binary: { name: "bytea" },
-
boolean: { name: "boolean" },
-
xml: { name: "xml" },
-
tsvector: { name: "tsvector" },
-
hstore: { name: "hstore" },
-
inet: { name: "inet" },
-
cidr: { name: "cidr" },
-
macaddr: { name: "macaddr" },
-
uuid: { name: "uuid" },
-
json: { name: "json" },
-
jsonb: { name: "jsonb" },
-
ltree: { name: "ltree" },
-
citext: { name: "citext" },
-
point: { name: "point" },
-
line: { name: "line" },
-
lseg: { name: "lseg" },
-
box: { name: "box" },
-
path: { name: "path" },
-
polygon: { name: "polygon" },
-
circle: { name: "circle" },
-
bit: { name: "bit" },
-
bit_varying: { name: "bit varying" },
-
money: { name: "money" },
-
interval: { name: "interval" },
-
oid: { name: "oid" },
-
}
-
-
2
OID = PostgreSQL::OID #:nodoc:
-
-
2
include PostgreSQL::Quoting
-
2
include PostgreSQL::ReferentialIntegrity
-
2
include PostgreSQL::SchemaStatements
-
2
include PostgreSQL::DatabaseStatements
-
-
2
def supports_bulk_alter?
-
54
true
-
end
-
-
2
def supports_index_sort_order?
-
490
true
-
end
-
-
2
def supports_partitioned_indexes?
-
7
database_version >= 110_000
-
end
-
-
2
def supports_partial_index?
-
501
true
-
end
-
-
2
def supports_expression_index?
-
6
true
-
end
-
-
2
def supports_transaction_isolation?
-
2
true
-
end
-
-
2
def supports_foreign_keys?
-
1734
true
-
end
-
-
2
def supports_check_constraints?
-
3671
true
-
end
-
-
2
def supports_validate_constraints?
-
11
true
-
end
-
-
2
def supports_views?
-
1
true
-
end
-
-
2
def supports_datetime_with_precision?
-
138
true
-
end
-
-
2
def supports_json?
-
true
-
end
-
-
2
def supports_comments?
-
1510
true
-
end
-
-
2
def supports_savepoints?
-
21
true
-
end
-
-
2
def supports_insert_returning?
-
117
true
-
end
-
-
2
def supports_insert_on_conflict?
-
86
database_version >= 90500
-
end
-
2
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
-
2
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
-
2
alias supports_insert_conflict_target? supports_insert_on_conflict?
-
-
2
def index_algorithms
-
11
{ concurrently: "CONCURRENTLY" }
-
end
-
-
2
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
-
2
def initialize(connection, max)
-
410
super(max)
-
410
@connection = connection
-
410
@counter = 0
-
end
-
-
2
def next_key
-
1869
"a#{@counter + 1}"
-
end
-
-
2
def []=(sql, key)
-
3734
super.tap { @counter += 1 }
-
end
-
-
2
private
-
2
def dealloc(key)
-
1859
@connection.query "DEALLOCATE #{key}" if connection_active?
-
rescue PG::Error
-
end
-
-
2
def connection_active?
-
1859
@connection.status == PG::CONNECTION_OK
-
rescue PG::Error
-
35
false
-
end
-
end
-
-
# Initializes and connects a PostgreSQL adapter.
-
2
def initialize(connection, logger, connection_parameters, config)
-
408
super(connection, logger, config)
-
-
408
@connection_parameters = connection_parameters
-
-
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
-
408
@local_tz = nil
-
408
@max_identifier_length = nil
-
-
408
configure_connection
-
408
add_pg_encoders
-
408
add_pg_decoders
-
-
408
@type_map = Type::HashLookupTypeMap.new
-
408
initialize_type_map
-
408
@local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
-
408
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
-
end
-
-
2
def self.database_exists?(config)
-
2
!!ActiveRecord::Base.postgresql_connection(config)
-
rescue ActiveRecord::NoDatabaseError
-
1
false
-
end
-
-
# Is this connection alive and ready for queries?
-
2
def active?
-
25381
@lock.synchronize do
-
25381
@connection.query "SELECT 1"
-
end
-
25378
true
-
rescue PG::Error
-
3
false
-
end
-
-
# Close then reopen the connection.
-
2
def reconnect!
-
257
@lock.synchronize do
-
257
super
-
257
@connection.reset
-
255
configure_connection
-
rescue PG::ConnectionBad
-
2
connect
-
end
-
end
-
-
2
def reset!
-
45
@lock.synchronize do
-
45
clear_cache!
-
45
reset_transaction
-
45
unless @connection.transaction_status == ::PG::PQTRANS_IDLE
-
1
@connection.query "ROLLBACK"
-
end
-
45
@connection.query "DISCARD ALL"
-
45
configure_connection
-
end
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
2
def disconnect!
-
259
@lock.synchronize do
-
259
super
-
259
@connection.close rescue nil
-
end
-
end
-
-
2
def discard! # :nodoc:
-
7
super
-
7
@connection.socket_io.reopen(IO::NULL) rescue nil
-
7
@connection = nil
-
end
-
-
2
def native_database_types #:nodoc:
-
14881
NATIVE_DATABASE_TYPES
-
end
-
-
2
def set_standard_conforming_strings
-
710
execute("SET standard_conforming_strings = on", "SCHEMA")
-
end
-
-
2
def supports_ddl_transactions?
-
137
true
-
end
-
-
2
def supports_advisory_locks?
-
117
true
-
end
-
-
2
def supports_explain?
-
2
true
-
end
-
-
2
def supports_extensions?
-
249
true
-
end
-
-
2
def supports_ranges?
-
1
true
-
end
-
2
deprecate :supports_ranges?
-
-
2
def supports_materialized_views?
-
1
true
-
end
-
-
2
def supports_foreign_tables?
-
1
true
-
end
-
-
2
def supports_pgcrypto_uuid?
-
42
database_version >= 90400
-
end
-
-
2
def supports_optimizer_hints?
-
2
unless defined?(@has_pg_hint_plan)
-
2
@has_pg_hint_plan = extension_available?("pg_hint_plan")
-
end
-
2
@has_pg_hint_plan
-
end
-
-
2
def supports_common_table_expressions?
-
1
true
-
end
-
-
2
def supports_lazy_transactions?
-
31108
true
-
end
-
-
2
def get_advisory_lock(lock_id) # :nodoc:
-
118
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
-
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
-
end
-
118
query_value("SELECT pg_try_advisory_lock(#{lock_id})")
-
end
-
-
2
def release_advisory_lock(lock_id) # :nodoc:
-
118
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
-
raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
-
end
-
118
query_value("SELECT pg_advisory_unlock(#{lock_id})")
-
end
-
-
2
def enable_extension(name)
-
112
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
-
112
reload_type_map
-
}
-
end
-
-
2
def disable_extension(name)
-
111
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
-
111
reload_type_map
-
}
-
end
-
-
2
def extension_available?(name)
-
3
query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
-
end
-
-
2
def extension_enabled?(name)
-
259
query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
-
end
-
-
2
def extensions
-
106
exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
-
end
-
-
# Returns the configured supported identifier length supported by PostgreSQL
-
2
def max_identifier_length
-
4873
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
-
end
-
-
# Set the authorized user for this session
-
2
def session_auth=(user)
-
56
clear_cache!
-
56
execute("SET SESSION AUTHORIZATION #{user}")
-
end
-
-
2
def use_insert_returning?
-
4344
@use_insert_returning
-
end
-
-
# Returns the version of the connected PostgreSQL server.
-
2
def get_database_version # :nodoc:
-
448
@connection.server_version
-
end
-
2
alias :postgresql_version :database_version
-
-
2
def default_index_type?(index) # :nodoc:
-
689
index.using == :btree || super
-
end
-
-
2
def build_insert_sql(insert) # :nodoc:
-
56
sql = +"INSERT #{insert.into} #{insert.values_list}"
-
-
55
if insert.skip_duplicates?
-
20
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
-
35
elsif insert.update_duplicates?
-
21
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
-
91
sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
-
63
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
-
end
-
-
55
sql << " RETURNING #{insert.returning}" if insert.returning
-
55
sql
-
end
-
-
2
def check_version # :nodoc:
-
403
if database_version < 90300
-
raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
-
end
-
end
-
-
2
private
-
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
-
2
VALUE_LIMIT_VIOLATION = "22001"
-
2
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
-
2
NOT_NULL_VIOLATION = "23502"
-
2
FOREIGN_KEY_VIOLATION = "23503"
-
2
UNIQUE_VIOLATION = "23505"
-
2
SERIALIZATION_FAILURE = "40001"
-
2
DEADLOCK_DETECTED = "40P01"
-
2
DUPLICATE_DATABASE = "42P04"
-
2
LOCK_NOT_AVAILABLE = "55P03"
-
2
QUERY_CANCELED = "57014"
-
-
2
def translate_exception(exception, message:, sql:, binds:)
-
1252
return exception unless exception.respond_to?(:result)
-
-
1250
case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
-
when UNIQUE_VIOLATION
-
13
RecordNotUnique.new(message, sql: sql, binds: binds)
-
when FOREIGN_KEY_VIOLATION
-
3
InvalidForeignKey.new(message, sql: sql, binds: binds)
-
when VALUE_LIMIT_VIOLATION
-
3
ValueTooLong.new(message, sql: sql, binds: binds)
-
when NUMERIC_VALUE_OUT_OF_RANGE
-
1
RangeError.new(message, sql: sql, binds: binds)
-
when NOT_NULL_VIOLATION
-
9
NotNullViolation.new(message, sql: sql, binds: binds)
-
when SERIALIZATION_FAILURE
-
1
SerializationFailure.new(message, sql: sql, binds: binds)
-
when DEADLOCK_DETECTED
-
1
Deadlocked.new(message, sql: sql, binds: binds)
-
when DUPLICATE_DATABASE
-
DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
-
when LOCK_NOT_AVAILABLE
-
1
LockWaitTimeout.new(message, sql: sql, binds: binds)
-
when QUERY_CANCELED
-
2
QueryCanceled.new(message, sql: sql, binds: binds)
-
else
-
1216
super
-
end
-
end
-
-
2
def get_oid_type(oid, fmod, column_name, sql_type = "")
-
150165
if !type_map.key?(oid)
-
94
load_additional_types([oid])
-
end
-
-
150165
type_map.fetch(oid, fmod, sql_type) {
-
5
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
-
5
Type.default_value.tap do |cast_type|
-
5
type_map.register_type(oid, cast_type)
-
end
-
}
-
end
-
-
2
def initialize_type_map(m = type_map)
-
632
m.register_type "int2", Type::Integer.new(limit: 2)
-
632
m.register_type "int4", Type::Integer.new(limit: 4)
-
632
m.register_type "int8", Type::Integer.new(limit: 8)
-
632
m.register_type "oid", OID::Oid.new
-
632
m.register_type "float4", Type::Float.new
-
632
m.alias_type "float8", "float4"
-
632
m.register_type "text", Type::Text.new
-
632
register_class_with_limit m, "varchar", Type::String
-
632
m.alias_type "char", "varchar"
-
632
m.alias_type "name", "varchar"
-
632
m.alias_type "bpchar", "varchar"
-
632
m.register_type "bool", Type::Boolean.new
-
632
register_class_with_limit m, "bit", OID::Bit
-
632
register_class_with_limit m, "varbit", OID::BitVarying
-
632
m.alias_type "timestamptz", "timestamp"
-
632
m.register_type "date", OID::Date.new
-
-
632
m.register_type "money", OID::Money.new
-
632
m.register_type "bytea", OID::Bytea.new
-
632
m.register_type "point", OID::Point.new
-
632
m.register_type "hstore", OID::Hstore.new
-
632
m.register_type "json", Type::Json.new
-
632
m.register_type "jsonb", OID::Jsonb.new
-
632
m.register_type "cidr", OID::Cidr.new
-
632
m.register_type "inet", OID::Inet.new
-
632
m.register_type "uuid", OID::Uuid.new
-
632
m.register_type "xml", OID::Xml.new
-
632
m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
-
632
m.register_type "macaddr", OID::Macaddr.new
-
632
m.register_type "citext", OID::SpecializedString.new(:citext)
-
632
m.register_type "ltree", OID::SpecializedString.new(:ltree)
-
632
m.register_type "line", OID::SpecializedString.new(:line)
-
632
m.register_type "lseg", OID::SpecializedString.new(:lseg)
-
632
m.register_type "box", OID::SpecializedString.new(:box)
-
632
m.register_type "path", OID::SpecializedString.new(:path)
-
632
m.register_type "polygon", OID::SpecializedString.new(:polygon)
-
632
m.register_type "circle", OID::SpecializedString.new(:circle)
-
-
632
m.register_type "interval" do |_, _, sql_type|
-
10
precision = extract_precision(sql_type)
-
10
OID::SpecializedString.new(:interval, precision: precision)
-
end
-
-
632
register_class_with_precision m, "time", Type::Time
-
632
register_class_with_precision m, "timestamp", OID::DateTime
-
-
632
m.register_type "numeric" do |_, fmod, sql_type|
-
350
precision = extract_precision(sql_type)
-
350
scale = extract_scale(sql_type)
-
-
# The type for the numeric depends on the width of the field,
-
# so we'll do something special here.
-
#
-
# When dealing with decimal columns:
-
#
-
# places after decimal = fmod - 4 & 0xffff
-
# places before decimal = (fmod - 4) >> 16 & 0xffff
-
350
if fmod && (fmod - 4 & 0xffff).zero?
-
# FIXME: Remove this class, and the second argument to
-
# lookups on PG
-
28
Type::DecimalWithoutScale.new(precision: precision)
-
else
-
322
OID::Decimal.new(precision: precision, scale: scale)
-
end
-
end
-
-
632
load_additional_types
-
end
-
-
# Extracts the value from a PostgreSQL column default definition.
-
2
def extract_value_from_default(default)
-
20923
case default
-
# Quoted types
-
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
-
# The default 'now'::date is CURRENT_DATE
-
591
if $1 == "now" && $2 == "date"
-
nil
-
else
-
591
$1.gsub("''", "'")
-
end
-
# Boolean types
-
when "true", "false"
-
176
default
-
# Numeric types
-
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
-
1911
$1
-
# Object identifier types
-
when /\A-?\d+\z/
-
$1
-
else
-
# Anything else is blank, some user type, or some function
-
# and we can't know the value of that, so return nil.
-
nil
-
end
-
end
-
-
2
def extract_default_function(default_value, default)
-
20923
default if has_default_function?(default_value, default)
-
end
-
-
2
def has_default_function?(default_value, default)
-
20923
!default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
-
end
-
-
2
def load_additional_types(oids = nil)
-
726
initializer = OID::TypeMapInitializer.new(type_map)
-
-
726
query = <<~SQL
-
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
-
FROM pg_type as t
-
LEFT JOIN pg_range as r ON oid = rngtypid
-
SQL
-
-
726
if oids
-
94
query += "WHERE t.oid IN (%s)" % oids.join(", ")
-
else
-
632
query += initializer.query_conditions_for_initial_load
-
end
-
-
726
execute_and_clear(query, "SCHEMA", []) do |records|
-
726
initializer.run(records)
-
end
-
end
-
-
2
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
-
-
2
def execute_and_clear(sql, name, binds, prepare: false)
-
26409
if preventing_writes? && write_query?(sql)
-
10
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
-
end
-
-
26399
if without_prepared_statement?(binds)
-
8750
result = exec_no_cache(sql, name, [])
-
17649
elsif !prepare
-
8932
result = exec_no_cache(sql, name, binds)
-
else
-
8717
result = exec_cache(sql, name, binds)
-
end
-
26328
begin
-
26328
ret = yield result
-
ensure
-
26328
result.clear
-
end
-
26328
ret
-
end
-
-
2
def exec_no_cache(sql, name, binds)
-
17682
materialize_transactions
-
17682
mark_transaction_written_if_write(sql)
-
-
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
-
# made since we established the connection
-
17682
update_typemap_for_default_timezone
-
-
17682
type_casted_binds = type_casted_binds(binds)
-
17682
log(sql, name, binds, type_casted_binds) do
-
17682
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
17682
@connection.exec_params(sql, type_casted_binds)
-
end
-
end
-
end
-
-
2
def exec_cache(sql, name, binds)
-
8717
materialize_transactions
-
8717
mark_transaction_written_if_write(sql)
-
8717
update_typemap_for_default_timezone
-
-
8717
stmt_key = prepare_statement(sql, binds)
-
8713
type_casted_binds = type_casted_binds(binds)
-
-
8707
log(sql, name, binds, type_casted_binds, stmt_key) do
-
8707
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
8707
@connection.exec_prepared(stmt_key, type_casted_binds)
-
end
-
end
-
rescue ActiveRecord::StatementInvalid => e
-
8
raise unless is_cached_plan_failure?(e)
-
-
# Nothing we can do if we are in a transaction because all commands
-
# will raise InFailedSQLTransaction
-
2
if in_transaction?
-
2
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
-
else
-
@lock.synchronize do
-
# outside of transactions we can simply flush this query and retry
-
@statements.delete sql_key(sql)
-
end
-
retry
-
end
-
end
-
-
# Annoyingly, the code for prepared statements whose return value may
-
# have changed is FEATURE_NOT_SUPPORTED.
-
#
-
# This covers various different error types so we need to do additional
-
# work to classify the exception definitively as a
-
# ActiveRecord::PreparedStatementCacheExpired
-
#
-
# Check here for more details:
-
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
-
2
def is_cached_plan_failure?(e)
-
8
pgerror = e.cause
-
8
pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
-
pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
-
rescue
-
false
-
end
-
-
2
def in_transaction?
-
2
open_transactions > 0
-
end
-
-
# Returns the statement identifier for the client side cache
-
# of statements
-
2
def sql_key(sql)
-
8728
"#{schema_search_path}-#{sql}"
-
end
-
-
# Prepare the statement if it hasn't been prepared, return
-
# the statement key.
-
2
def prepare_statement(sql, binds)
-
8717
@lock.synchronize do
-
8717
sql_key = sql_key(sql)
-
8717
unless @statements.key? sql_key
-
1869
nextkey = @statements.next_key
-
1869
begin
-
1869
@connection.prepare nextkey, sql
-
rescue => e
-
4
raise translate_exception_class(e, sql, binds)
-
end
-
# Clear the queue
-
1865
@connection.get_last_result
-
1865
@statements[sql_key] = nextkey
-
end
-
8713
@statements[sql_key]
-
end
-
end
-
-
# Connects to a PostgreSQL server and sets up the adapter depending on the
-
# connected server's characteristics.
-
2
def connect
-
2
@connection = PG.connect(@connection_parameters)
-
2
configure_connection
-
2
add_pg_encoders
-
2
add_pg_decoders
-
end
-
-
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
-
# This is called by #connect and should not be called manually.
-
2
def configure_connection
-
710
if @config[:encoding]
-
@connection.set_client_encoding(@config[:encoding])
-
end
-
710
self.client_min_messages = @config[:min_messages] || "warning"
-
710
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
-
-
# Use standard-conforming strings so we don't have to do the E'...' dance.
-
710
set_standard_conforming_strings
-
-
710
variables = @config.fetch(:variables, {}).stringify_keys
-
-
# If using Active Record's time zone support configure the connection to return
-
# TIMESTAMP WITH ZONE types in UTC.
-
710
unless variables["timezone"]
-
709
if ActiveRecord::Base.default_timezone == :utc
-
708
variables["timezone"] = "UTC"
-
1
elsif @local_tz
-
1
variables["timezone"] = @local_tz
-
end
-
end
-
-
# SET statements from :variables config hash
-
# https://www.postgresql.org/docs/current/static/sql-set.html
-
710
variables.map do |k, v|
-
714
if v == ":default" || v == :default
-
# Sets the value to the global or compile default
-
1
execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
-
713
elsif !v.nil?
-
712
execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
-
end
-
end
-
end
-
-
# Returns the list of a table's column names, data types, and default values.
-
#
-
# The underlying query is roughly:
-
# SELECT column.name, column.type, default.value, column.comment
-
# FROM column LEFT JOIN default
-
# ON column.table_id = default.table_id
-
# AND column.num = default.column_num
-
# WHERE column.table_id = get_table_id('table_name')
-
# AND column.num > 0
-
# AND NOT column.is_dropped
-
# ORDER BY column.num
-
#
-
# If the table name is not prefixed with a schema, the database will
-
# take the first match from the schema search path.
-
#
-
# Query implementation notes:
-
# - format_type includes the column size constraint, e.g. varchar(50)
-
# - ::regclass is a function that gives the id for a table name
-
2
def column_definitions(table_name)
-
4657
query(<<~SQL, "SCHEMA")
-
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
-
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
-
c.collname, col_description(a.attrelid, a.attnum) AS comment
-
FROM pg_attribute a
-
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
-
LEFT JOIN pg_type t ON a.atttypid = t.oid
-
LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
-
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
-
AND a.attnum > 0 AND NOT a.attisdropped
-
ORDER BY a.attnum
-
SQL
-
end
-
-
2
def extract_table_ref_from_insert_sql(sql)
-
17
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
-
17
$1.strip if $1
-
end
-
-
2
def arel_visitor
-
408
Arel::Visitors::PostgreSQL.new(self)
-
end
-
-
2
def build_statement_pool
-
408
StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
-
end
-
-
2
def can_perform_case_insensitive_comparison_for?(column)
-
27
@case_insensitive_cache ||= {}
-
27
@case_insensitive_cache[column.sql_type] ||= begin
-
13
sql = <<~SQL
-
SELECT exists(
-
SELECT * FROM pg_proc
-
WHERE proname = 'lower'
-
AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
-
) OR exists(
-
SELECT * FROM pg_proc
-
INNER JOIN pg_cast
-
ON ARRAY[casttarget]::oidvector = proargtypes
-
WHERE proname = 'lower'
-
AND castsource = #{quote column.sql_type}::regtype
-
)
-
SQL
-
13
execute_and_clear(sql, "SCHEMA", []) do |result|
-
13
result.getvalue(0, 0)
-
end
-
end
-
end
-
-
2
def add_pg_encoders
-
410
map = PG::TypeMapByClass.new
-
410
map[Integer] = PG::TextEncoder::Integer.new
-
410
map[TrueClass] = PG::TextEncoder::Boolean.new
-
410
map[FalseClass] = PG::TextEncoder::Boolean.new
-
410
@connection.type_map_for_queries = map
-
end
-
-
2
def update_typemap_for_default_timezone
-
26809
if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
-
430
decoder_class = ActiveRecord::Base.default_timezone == :utc ?
-
PG::TextDecoder::TimestampUtc :
-
PG::TextDecoder::TimestampWithoutTimeZone
-
-
430
@timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
-
430
@connection.type_map_for_results.add_coder(@timestamp_decoder)
-
430
@default_timezone = ActiveRecord::Base.default_timezone
-
end
-
end
-
-
2
def add_pg_decoders
-
410
@default_timezone = nil
-
410
@timestamp_decoder = nil
-
-
410
coders_by_name = {
-
"int2" => PG::TextDecoder::Integer,
-
"int4" => PG::TextDecoder::Integer,
-
"int8" => PG::TextDecoder::Integer,
-
"oid" => PG::TextDecoder::Integer,
-
"float4" => PG::TextDecoder::Float,
-
"float8" => PG::TextDecoder::Float,
-
"numeric" => PG::TextDecoder::Numeric,
-
"bool" => PG::TextDecoder::Boolean,
-
"timestamp" => PG::TextDecoder::TimestampUtc,
-
"timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
-
}
-
-
4510
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
-
410
query = <<~SQL % known_coder_types.join(", ")
-
SELECT t.oid, t.typname
-
FROM pg_type as t
-
WHERE t.typname IN (%s)
-
SQL
-
410
coders = execute_and_clear(query, "SCHEMA", []) do |result|
-
result
-
4100
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
-
410
.compact
-
end
-
-
410
map = PG::TypeMapByOid.new
-
4510
coders.each { |coder| map.add_coder(coder) }
-
410
@connection.type_map_for_results = map
-
-
410
@type_map_for_results = PG::TypeMapByOid.new
-
410
@type_map_for_results.default_type_map = map
-
410
@type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
-
410
@type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
-
-
# extract timestamp decoder for use in update_typemap_for_default_timezone
-
3690
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
-
410
update_typemap_for_default_timezone
-
end
-
-
2
def construct_coder(row, coder_class)
-
4100
return unless coder_class
-
4100
coder_class.new(oid: row["oid"].to_i, name: row["typname"])
-
end
-
-
2
class MoneyDecoder < PG::SimpleDecoder # :nodoc:
-
2
TYPE = OID::Money.new
-
-
2
def decode(value, tuple = nil, field = nil)
-
2
TYPE.deserialize(value)
-
end
-
end
-
-
2
ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
-
2
ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
-
2
ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/file/atomic"
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
class SchemaCache
-
3
def self.load_from(filename)
-
21
return unless File.file?(filename)
-
-
21
read(filename) do |file|
-
21
filename.include?(".dump") ? Marshal.load(file) : YAML.load(file)
-
end
-
end
-
-
3
def self.read(filename, &block)
-
21
if File.extname(filename) == ".gz"
-
6
Zlib::GzipReader.open(filename) { |gz|
-
6
yield gz.read
-
}
-
else
-
15
yield File.read(filename)
-
end
-
end
-
3
private_class_method :read
-
-
3
attr_reader :version
-
3
attr_accessor :connection
-
-
3
def initialize(conn)
-
1154
@connection = conn
-
-
1154
@columns = {}
-
1154
@columns_hash = {}
-
1154
@primary_keys = {}
-
1154
@data_sources = {}
-
1154
@indexes = {}
-
end
-
-
3
def initialize_dup(other)
-
super
-
@columns = @columns.dup
-
@columns_hash = @columns_hash.dup
-
@primary_keys = @primary_keys.dup
-
@data_sources = @data_sources.dup
-
@indexes = @indexes.dup
-
end
-
-
3
def encode_with(coder)
-
9
reset_version!
-
-
9
coder["columns"] = @columns
-
9
coder["primary_keys"] = @primary_keys
-
9
coder["data_sources"] = @data_sources
-
9
coder["indexes"] = @indexes
-
9
coder["version"] = @version
-
9
coder["database_version"] = database_version
-
end
-
-
3
def init_with(coder)
-
18
@columns = coder["columns"]
-
18
@primary_keys = coder["primary_keys"]
-
18
@data_sources = coder["data_sources"]
-
18
@indexes = coder["indexes"] || {}
-
18
@version = coder["version"]
-
18
@database_version = coder["database_version"]
-
-
18
derive_columns_hash_and_deduplicate_values
-
end
-
-
3
def primary_keys(table_name)
-
5148
@primary_keys.fetch(table_name) do
-
3933
if data_source_exists?(table_name)
-
3930
@primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
-
end
-
end
-
end
-
-
# A cached lookup for table existence.
-
3
def data_source_exists?(name)
-
13944
prepare_data_sources if @data_sources.empty?
-
13944
return @data_sources[name] if @data_sources.key? name
-
-
960
@data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
-
end
-
-
# Add internal cache for table with +table_name+.
-
3
def add(table_name)
-
2958
if data_source_exists?(table_name)
-
2957
primary_keys(table_name)
-
2957
columns(table_name)
-
2957
columns_hash(table_name)
-
2957
indexes(table_name)
-
end
-
end
-
-
3
def data_sources(name)
-
27
@data_sources[name]
-
end
-
-
# Get the columns for a table
-
3
def columns(table_name)
-
8971
@columns.fetch(table_name) do
-
5972
@columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
-
end
-
end
-
-
# Get the columns for a table as a hash, key is the column name
-
# value is the column object.
-
3
def columns_hash(table_name)
-
160901
@columns_hash.fetch(table_name) do
-
5969
@columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze
-
end
-
end
-
-
# Checks whether the columns hash is already cached for a table.
-
3
def columns_hash?(table_name)
-
12
@columns_hash.key?(table_name)
-
end
-
-
3
def indexes(table_name)
-
3169
@indexes.fetch(table_name) do
-
2957
@indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
-
end
-
end
-
-
3
def database_version # :nodoc:
-
1227
@database_version ||= connection.get_database_version
-
end
-
-
# Clears out internal caches
-
3
def clear!
-
299
@columns.clear
-
299
@columns_hash.clear
-
299
@primary_keys.clear
-
299
@data_sources.clear
-
299
@indexes.clear
-
299
@version = nil
-
299
@database_version = nil
-
end
-
-
3
def size
-
21
[@columns, @columns_hash, @primary_keys, @data_sources].sum(&:size)
-
end
-
-
# Clear out internal caches for the data source +name+.
-
3
def clear_data_source_cache!(name)
-
15752
@columns.delete name
-
15752
@columns_hash.delete name
-
15752
@primary_keys.delete name
-
15752
@data_sources.delete name
-
15752
@indexes.delete name
-
end
-
-
3
def dump_to(filename)
-
15
clear!
-
2943
connection.data_sources.each { |table| add(table) }
-
15
open(filename) { |f|
-
15
if filename.include?(".dump")
-
6
f.write(Marshal.dump(self))
-
else
-
9
f.write(YAML.dump(self))
-
end
-
}
-
end
-
-
3
def marshal_dump
-
9
reset_version!
-
-
9
[@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
-
end
-
-
3
def marshal_load(array)
-
12
@version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
-
12
@indexes ||= {}
-
-
12
derive_columns_hash_and_deduplicate_values
-
end
-
-
3
private
-
3
def reset_version!
-
18
@version = connection.migration_context.current_version
-
end
-
-
3
def derive_columns_hash_and_deduplicate_values
-
30
@columns = deep_deduplicate(@columns)
-
3558
@columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
-
30
@primary_keys = deep_deduplicate(@primary_keys)
-
30
@data_sources = deep_deduplicate(@data_sources)
-
30
@indexes = deep_deduplicate(@indexes)
-
end
-
-
3
def deep_deduplicate(value)
-
110723
case value
-
when Hash
-
32552
value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
-
when Array
-
61539
value.map { |i| deep_deduplicate(i) }
-
when String, Deduplicable
-
85900
-value
-
else
-
8688
value
-
end
-
end
-
-
3
def prepare_data_sources
-
58477
connection.data_sources.each { |source| @data_sources[source] = true }
-
end
-
-
3
def open(filename)
-
15
File.atomic_write(filename) do |file|
-
15
if File.extname(filename) == ".gz"
-
6
zipper = Zlib::GzipWriter.new file
-
6
yield zipper
-
6
zipper.flush
-
6
zipper.close
-
else
-
9
yield file
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/connection_adapters/deduplicable"
-
-
3
module ActiveRecord
-
# :stopdoc:
-
3
module ConnectionAdapters
-
3
class SqlTypeMetadata
-
3
include Deduplicable
-
-
3
attr_reader :sql_type, :type, :limit, :precision, :scale
-
-
3
def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
-
247314
@sql_type = sql_type
-
247314
@type = type
-
247314
@limit = limit
-
247314
@precision = precision
-
247314
@scale = scale
-
end
-
-
3
def ==(other)
-
525938
other.is_a?(SqlTypeMetadata) &&
-
sql_type == other.sql_type &&
-
type == other.type &&
-
limit == other.limit &&
-
precision == other.precision &&
-
scale == other.scale
-
end
-
3
alias eql? ==
-
-
3
def hash
-
SqlTypeMetadata.hash ^
-
sql_type.hash ^
-
type.hash ^
-
limit.hash ^
-
564641
precision.hash >> 1 ^
-
scale.hash >> 2
-
end
-
-
3
private
-
3
def deduplicated
-
235
@sql_type = -sql_type
-
235
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module SQLite3
-
3
module DatabaseStatements
-
3
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
-
:pragma
-
) # :nodoc:
-
3
private_constant :READ_QUERY
-
-
3
def write_query?(sql) # :nodoc:
-
71257
!READ_QUERY.match?(sql)
-
end
-
-
3
def explain(arel, binds = [])
-
10
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
-
10
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
-
end
-
-
3
def execute(sql, name = nil) #:nodoc:
-
48455
if preventing_writes? && write_query?(sql)
-
8
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
-
end
-
-
48447
materialize_transactions
-
48447
mark_transaction_written_if_write(sql)
-
-
48447
log(sql, name) do
-
48447
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
48447
@connection.execute(sql)
-
end
-
end
-
end
-
-
3
def exec_query(sql, name = nil, binds = [], prepare: false)
-
141898
if preventing_writes? && write_query?(sql)
-
18
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
-
end
-
-
141880
materialize_transactions
-
141880
mark_transaction_written_if_write(sql)
-
-
141880
type_casted_binds = type_casted_binds(binds)
-
-
141870
log(sql, name, binds, type_casted_binds) do
-
141870
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
# Don't cache statements if they are not prepared
-
141870
unless prepare
-
124326
stmt = @connection.prepare(sql)
-
124290
begin
-
124290
cols = stmt.columns
-
124290
unless without_prepared_statement?(binds)
-
16814
stmt.bind_params(type_casted_binds)
-
end
-
124290
records = stmt.to_a
-
ensure
-
124290
stmt.close
-
end
-
else
-
17544
stmt = @statements[sql] ||= @connection.prepare(sql)
-
17536
cols = stmt.columns
-
17536
stmt.reset!
-
17536
stmt.bind_params(type_casted_binds)
-
17536
records = stmt.to_a
-
end
-
-
141784
build_result(columns: cols, rows: records)
-
end
-
end
-
end
-
-
3
def exec_delete(sql, name = "SQL", binds = [])
-
4975
exec_query(sql, name, binds)
-
4929
@connection.changes
-
end
-
3
alias :exec_update :exec_delete
-
-
3
def begin_isolated_db_transaction(isolation) #:nodoc
-
14
raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
-
10
raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
-
-
8
Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
-
8
@connection.read_uncommitted = true
-
8
begin_db_transaction
-
end
-
-
3
def begin_db_transaction #:nodoc:
-
111116
log("begin transaction", "TRANSACTION") { @connection.transaction }
-
end
-
-
3
def commit_db_transaction #:nodoc:
-
6320
log("commit transaction", "TRANSACTION") { @connection.commit }
-
3160
reset_read_uncommitted
-
end
-
-
3
def exec_rollback_db_transaction #:nodoc:
-
104740
log("rollback transaction", "TRANSACTION") { @connection.rollback }
-
52370
reset_read_uncommitted
-
end
-
-
3
private
-
3
def reset_read_uncommitted
-
55530
read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
-
55530
return unless read_uncommitted
-
-
42163
@connection.read_uncommitted = read_uncommitted
-
end
-
-
3
def execute_batch(statements, name = nil)
-
940
sql = combine_multi_statements(statements)
-
-
940
if preventing_writes? && write_query?(sql)
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
-
end
-
-
940
materialize_transactions
-
940
mark_transaction_written_if_write(sql)
-
-
940
log(sql, name) do
-
940
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-
940
@connection.execute_batch2(sql)
-
end
-
end
-
end
-
-
3
def last_inserted_id(result)
-
7777
@connection.last_insert_row_id
-
end
-
-
3
def build_fixture_statements(fixture_set)
-
fixture_set.flat_map do |table_name, fixtures|
-
4061
next if fixtures.empty?
-
154818
fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
-
935
end.compact
-
end
-
-
3
def build_truncate_statement(table_name)
-
586
"DELETE FROM #{quote_table_name(table_name)}"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module SQLite3
-
3
class ExplainPrettyPrinter # :nodoc:
-
# Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
-
# the output of the SQLite shell:
-
#
-
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
-
# 0|1|1|SCAN TABLE posts (~100000 rows)
-
#
-
3
def pp(result)
-
result.rows.map do |row|
-
10
row.join("|")
-
10
end.join("\n") + "\n"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module SQLite3
-
3
module Quoting # :nodoc:
-
3
def quote_string(s)
-
97748
@connection.class.quote(s)
-
end
-
-
3
def quote_table_name_for_assignment(table, attr)
-
quote_column_name(attr)
-
end
-
-
3
def quote_table_name(name)
-
342066
self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
-
end
-
-
3
def quote_column_name(name)
-
584325
self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
-
end
-
-
3
def quoted_time(value)
-
385
value = value.change(year: 2000, month: 1, day: 1)
-
385
quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
-
end
-
-
3
def quoted_binary(value)
-
136
"x'#{value.hex}'"
-
end
-
-
3
def quoted_true
-
1548
"1"
-
end
-
-
3
def unquoted_true
-
277
1
-
end
-
-
3
def quoted_false
-
487
"0"
-
end
-
-
3
def unquoted_false
-
198
0
-
end
-
-
3
def column_name_matcher
-
1238
COLUMN_NAME
-
end
-
-
3
def column_name_with_order_matcher
-
9035
COLUMN_NAME_WITH_ORDER
-
end
-
-
3
COLUMN_NAME = /
-
\A
-
(
-
(?:
-
# "table_name"."column_name" | function(one or no argument)
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
-
)
-
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
3
COLUMN_NAME_WITH_ORDER = /
-
\A
-
(
-
(?:
-
# "table_name"."column_name" | function(one or no argument)
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
-
)
-
(?:\s+ASC|\s+DESC)?
-
)
-
(?:\s*,\s*\g<1>)*
-
\z
-
/ix
-
-
3
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
-
-
3
private
-
3
def _type_cast(value)
-
79567
case value
-
when BigDecimal
-
30
value.to_f
-
when String
-
17560
if value.encoding == Encoding::ASCII_8BIT
-
6
super(value.encode(Encoding::UTF_8))
-
else
-
17554
super
-
end
-
else
-
61977
super
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module SQLite3
-
3
class SchemaCreation < SchemaCreation # :nodoc:
-
3
private
-
3
def supports_index_using?
-
9844
false
-
end
-
-
3
def add_column_options!(sql, options)
-
39875
if options[:collation]
-
46
sql << " COLLATE \"#{options[:collation]}\""
-
end
-
39875
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module SQLite3
-
3
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
-
3
def references(*args, **options)
-
667
super(*args, type: :integer, **options)
-
end
-
3
alias :belongs_to :references
-
-
3
private
-
3
def integer_like_primary_key_type(type, options)
-
36
:primary_key
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module SQLite3
-
3
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
-
3
private
-
3
def default_primary_key?(column)
-
2105
schema_type(column) == :integer
-
end
-
-
3
def explicit_primary_key_default?(column)
-
2105
column.bigint?
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
module SQLite3
-
3
module SchemaStatements # :nodoc:
-
# Returns an array of indexes for the given table.
-
3
def indexes(table_name)
-
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
-
# Indexes SQLite creates implicitly for internal use start with "sqlite_".
-
# See https://www.sqlite.org/fileformat2.html#intschema
-
10937
next if row["name"].start_with?("sqlite_")
-
-
10766
index_sql = query_value(<<~SQL, "SCHEMA")
-
SELECT sql
-
FROM sqlite_master
-
WHERE name = #{quote(row['name'])} AND type = 'index'
-
UNION ALL
-
SELECT sql
-
FROM sqlite_temp_master
-
WHERE name = #{quote(row['name'])} AND type = 'index'
-
SQL
-
-
10766
/\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
-
-
10766
columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
-
11225
col["name"]
-
end
-
-
10766
orders = {}
-
-
10766
if columns.any?(&:nil?) # index created with an expression
-
73
columns = expressions
-
else
-
# Add info on sort order for columns (only desc order is explicitly specified,
-
# asc is the default)
-
10693
if index_sql # index_sql can be null in case of primary key indexes
-
10693
index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
-
112
orders[order_column] = :desc
-
}
-
end
-
end
-
-
10766
IndexDefinition.new(
-
table_name,
-
row["name"],
-
row["unique"] != 0,
-
columns,
-
where: where,
-
orders: orders
-
)
-
7460
end.compact
-
end
-
-
3
def add_foreign_key(from_table, to_table, **options)
-
78
alter_table(from_table) do |definition|
-
78
to_table = strip_table_name_prefix_and_suffix(to_table)
-
78
definition.foreign_key(to_table, **options)
-
end
-
end
-
-
3
def remove_foreign_key(from_table, to_table = nil, **options)
-
48
to_table ||= options[:to_table]
-
48
options = options.except(:name, :to_table, :validate)
-
48
foreign_keys = foreign_keys(from_table)
-
-
48
fkey = foreign_keys.detect do |fk|
-
48
table = to_table || begin
-
16
table = options[:column].to_s.delete_suffix("_id")
-
16
Base.pluralize_table_names ? table.pluralize : table
-
end
-
48
table = strip_table_name_prefix_and_suffix(table)
-
48
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
-
88
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
-
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
-
-
46
foreign_keys.delete(fkey)
-
46
alter_table(from_table, foreign_keys)
-
end
-
-
3
def check_constraints(table_name)
-
3796
table_sql = query_value(<<-SQL, "SCHEMA")
-
SELECT sql
-
FROM sqlite_master
-
WHERE name = #{quote_table_name(table_name)} AND type = 'table'
-
UNION ALL
-
SELECT sql
-
FROM sqlite_temp_master
-
WHERE name = #{quote_table_name(table_name)} AND type = 'table'
-
SQL
-
-
3796
table_sql.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
-
36
CheckConstraintDefinition.new(table_name, expression, name: name)
-
end
-
end
-
-
3
def add_check_constraint(table_name, expression, **options)
-
13
alter_table(table_name) do |definition|
-
13
definition.check_constraint(expression, **options)
-
end
-
end
-
-
3
def remove_check_constraint(table_name, expression = nil, **options)
-
4
check_constraints = check_constraints(table_name)
-
4
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
-
6
check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
-
2
alter_table(table_name, foreign_keys(table_name), check_constraints)
-
end
-
-
3
def create_schema_dumper(options)
-
88
SQLite3::SchemaDumper.create(self, options)
-
end
-
-
3
private
-
3
def schema_creation
-
15425
SQLite3::SchemaCreation.new(self)
-
end
-
-
3
def create_table_definition(name, **options)
-
5591
SQLite3::TableDefinition.new(self, name, **options)
-
end
-
-
3
def validate_index_length!(table_name, new_name, internal = false)
-
9868
super unless internal
-
end
-
-
3
def new_column_from_field(table_name, field)
-
226337
default = \
-
case field["dflt_value"]
-
when /^null$/i
-
106298
nil
-
when /^'(.*)'$/m
-
628
$1.gsub("''", "'")
-
when /^"(.*)"$/m
-
$1.gsub('""', '"')
-
else
-
119411
field["dflt_value"]
-
end
-
-
226337
type_metadata = fetch_type_metadata(field["type"])
-
226337
Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
-
end
-
-
3
def data_source_sql(name = nil, type: nil)
-
1109
scope = quoted_scope(name, type: type)
-
1109
scope[:type] ||= "'table','view'"
-
-
1109
sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
-
1109
sql << " AND name = #{scope[:name]}" if scope[:name]
-
1109
sql << " AND type IN (#{scope[:type]})"
-
1109
sql
-
end
-
-
3
def quoted_scope(name = nil, type: nil)
-
1109
type = \
-
case type
-
when "BASE TABLE"
-
297
"'table'"
-
when "VIEW"
-
42
"'view'"
-
end
-
1109
scope = {}
-
1109
scope[:name] = quote(name) if name
-
1109
scope[:type] = type if type
-
1109
scope
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/connection_adapters/abstract_adapter"
-
3
require "active_record/connection_adapters/statement_pool"
-
3
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
-
3
require "active_record/connection_adapters/sqlite3/quoting"
-
3
require "active_record/connection_adapters/sqlite3/database_statements"
-
3
require "active_record/connection_adapters/sqlite3/schema_creation"
-
3
require "active_record/connection_adapters/sqlite3/schema_definitions"
-
3
require "active_record/connection_adapters/sqlite3/schema_dumper"
-
3
require "active_record/connection_adapters/sqlite3/schema_statements"
-
-
3
gem "sqlite3", "~> 1.4"
-
3
require "sqlite3"
-
-
3
module ActiveRecord
-
3
module ConnectionHandling # :nodoc:
-
3
def sqlite3_connection(config)
-
603
config = config.symbolize_keys
-
-
# Require database.
-
603
unless config[:database]
-
raise ArgumentError, "No database file specified. Missing argument: database"
-
end
-
-
# Allow database path relative to Rails.root, but only if the database
-
# path is not the special path that tells sqlite to build a database only
-
# in memory.
-
603
if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:")
-
289
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
-
289
dirname = File.dirname(config[:database])
-
289
Dir.mkdir(dirname) unless File.directory?(dirname)
-
end
-
-
601
db = SQLite3::Database.new(
-
config[:database].to_s,
-
config.merge(results_as_hash: true)
-
)
-
-
601
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
-
rescue Errno::ENOENT => error
-
2
if error.message.include?("No such file or directory")
-
2
raise ActiveRecord::NoDatabaseError
-
else
-
raise
-
end
-
end
-
end
-
-
3
module ConnectionAdapters #:nodoc:
-
# The SQLite3 adapter works with the sqlite3-ruby drivers
-
# (available as gem from https://rubygems.org/gems/sqlite3).
-
#
-
# Options:
-
#
-
# * <tt>:database</tt> - Path to the database file.
-
3
class SQLite3Adapter < AbstractAdapter
-
3
ADAPTER_NAME = "SQLite"
-
-
3
include SQLite3::Quoting
-
3
include SQLite3::SchemaStatements
-
3
include SQLite3::DatabaseStatements
-
-
3
NATIVE_DATABASE_TYPES = {
-
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
-
string: { name: "varchar" },
-
text: { name: "text" },
-
integer: { name: "integer" },
-
float: { name: "float" },
-
decimal: { name: "decimal" },
-
datetime: { name: "datetime" },
-
time: { name: "time" },
-
date: { name: "date" },
-
binary: { name: "blob" },
-
boolean: { name: "boolean" },
-
json: { name: "json" },
-
}
-
-
3
def self.represent_boolean_as_integer=(value) # :nodoc:
-
if value == false
-
raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
-
end
-
-
ActiveSupport::Deprecation.warn(
-
"`.represent_boolean_as_integer=` is now always true, so setting this is deprecated and will be removed in Rails 6.1."
-
)
-
end
-
-
3
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
-
3
private
-
3
def dealloc(stmt)
-
3651
stmt.close unless stmt.closed?
-
end
-
end
-
-
3
def initialize(connection, logger, connection_options, config)
-
601
super(connection, logger, config)
-
601
configure_connection
-
end
-
-
3
def self.database_exists?(config)
-
6
config = config.symbolize_keys
-
6
if config[:database] == ":memory:"
-
3
true
-
else
-
3
database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
-
3
File.exist?(database_file)
-
end
-
end
-
-
3
def supports_ddl_transactions?
-
265
true
-
end
-
-
3
def supports_savepoints?
-
42
true
-
end
-
-
3
def supports_transaction_isolation?
-
4
true
-
end
-
-
3
def supports_partial_index?
-
9848
true
-
end
-
-
3
def supports_expression_index?
-
16
database_version >= "3.9.0"
-
end
-
-
3
def requires_reloading?
-
40
true
-
end
-
-
3
def supports_foreign_keys?
-
5310
true
-
end
-
-
3
def supports_check_constraints?
-
7547
true
-
end
-
-
3
def supports_views?
-
2
true
-
end
-
-
3
def supports_datetime_with_precision?
-
280
true
-
end
-
-
3
def supports_json?
-
true
-
end
-
-
3
def supports_common_table_expressions?
-
2
database_version >= "3.8.3"
-
end
-
-
3
def supports_insert_on_conflict?
-
168
database_version >= "3.24.0"
-
end
-
3
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
-
3
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
-
3
alias supports_insert_conflict_target? supports_insert_on_conflict?
-
-
3
def active?
-
54528
!@connection.closed?
-
end
-
-
3
def reconnect!
-
3
super
-
3
connect if @connection.closed?
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
3
def disconnect!
-
361
super
-
361
@connection.close rescue nil
-
end
-
-
3
def supports_index_sort_order?
-
9824
true
-
end
-
-
3
def native_database_types #:nodoc:
-
51170
NATIVE_DATABASE_TYPES
-
end
-
-
# Returns the current database encoding format as a string, eg: 'UTF-8'
-
3
def encoding
-
2
@connection.encoding.to_s
-
end
-
-
3
def supports_explain?
-
4
true
-
end
-
-
3
def supports_lazy_transactions?
-
64239
true
-
end
-
-
# REFERENTIAL INTEGRITY ====================================
-
-
3
def disable_referential_integrity # :nodoc:
-
2390
old_foreign_keys = query_value("PRAGMA foreign_keys")
-
2390
old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
-
-
2390
begin
-
2390
execute("PRAGMA defer_foreign_keys = ON")
-
2390
execute("PRAGMA foreign_keys = OFF")
-
2390
yield
-
ensure
-
2390
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
-
2390
execute("PRAGMA foreign_keys = #{old_foreign_keys}")
-
end
-
end
-
-
# SCHEMA STATEMENTS ========================================
-
-
3
def primary_keys(table_name) # :nodoc:
-
62002
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
-
22357
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
-
end
-
-
3
def remove_index(table_name, column_name = nil, **options) # :nodoc:
-
96
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
-
-
94
index_name = index_name_for_remove(table_name, column_name, options)
-
-
84
exec_query "DROP INDEX #{quote_column_name(index_name)}"
-
end
-
-
# Renames a table.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
3
def rename_table(table_name, new_name)
-
16
schema_cache.clear_data_source_cache!(table_name.to_s)
-
16
schema_cache.clear_data_source_cache!(new_name.to_s)
-
16
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
-
16
rename_table_indexes(table_name, new_name)
-
end
-
-
3
def add_column(table_name, column_name, type, **options) #:nodoc:
-
397
if invalid_alter_table_type?(type, options)
-
16
alter_table(table_name) do |definition|
-
16
definition.column(column_name, type, **options)
-
end
-
else
-
381
super
-
end
-
end
-
-
3
def remove_column(table_name, column_name, type = nil, **options) #:nodoc:
-
1164
alter_table(table_name) do |definition|
-
1164
definition.remove_column column_name
-
1164
definition.foreign_keys.delete_if do |_, fk_options|
-
10
fk_options[:column] == column_name.to_s
-
end
-
end
-
end
-
-
3
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
-
22
default = extract_new_default_value(default_or_changes)
-
-
22
alter_table(table_name) do |definition|
-
22
definition[column_name].default = default
-
end
-
end
-
-
3
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
-
12
unless null || default.nil?
-
2
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
-
end
-
12
alter_table(table_name) do |definition|
-
12
definition[column_name].null = null
-
end
-
end
-
-
3
def change_column(table_name, column_name, type, **options) #:nodoc:
-
44
alter_table(table_name) do |definition|
-
44
definition[column_name].instance_eval do
-
44
self.type = type
-
44
self.options.merge!(options)
-
end
-
end
-
end
-
-
3
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
38
column = column_for(table_name, column_name)
-
36
alter_table(table_name, rename: { column.name => new_column_name.to_s })
-
36
rename_column_indexes(table_name, column.name, new_column_name)
-
end
-
-
3
def add_reference(table_name, ref_name, **options) # :nodoc:
-
50
super(table_name, ref_name, type: :integer, **options)
-
end
-
3
alias :add_belongs_to :add_reference
-
-
3
def foreign_keys(table_name)
-
3912
fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
-
3912
fk_info.map do |row|
-
304
options = {
-
column: row["from"],
-
primary_key: row["to"],
-
on_delete: extract_foreign_key_action(row["on_delete"]),
-
on_update: extract_foreign_key_action(row["on_update"])
-
}
-
304
ForeignKeyDefinition.new(table_name, row["table"], options)
-
end
-
end
-
-
3
def build_insert_sql(insert) # :nodoc:
-
102
sql = +"INSERT #{insert.into} #{insert.values_list}"
-
-
100
if insert.skip_duplicates?
-
40
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
-
60
elsif insert.update_duplicates?
-
40
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
-
180
sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
-
120
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
-
end
-
-
100
sql
-
end
-
-
3
def shared_cache? # :nodoc:
-
14
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
-
end
-
-
3
def get_database_version # :nodoc:
-
504
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
-
end
-
-
3
def check_version # :nodoc:
-
441
if database_version < "3.8.0"
-
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
-
end
-
end
-
-
3
private
-
# See https://www.sqlite.org/limits.html,
-
# the default value is 999 when not configured.
-
3
def bind_params_length
-
33829
999
-
end
-
-
3
def initialize_type_map(m = type_map)
-
85
super
-
85
register_class_with_limit m, %r(int)i, SQLite3Integer
-
end
-
-
3
def table_structure(table_name)
-
28820
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
-
28820
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
-
28820
table_structure_with_collation(table_name, structure)
-
end
-
3
alias column_definitions table_structure
-
-
# See: https://www.sqlite.org/lang_altertable.html
-
# SQLite has an additional restriction on the ALTER TABLE statement
-
3
def invalid_alter_table_type?(type, options)
-
397
type.to_sym == :primary_key || options[:primary_key] ||
-
options[:null] == false && options[:default].nil?
-
end
-
-
3
def alter_table(
-
table_name,
-
foreign_keys = foreign_keys(table_name),
-
check_constraints = check_constraints(table_name),
-
**options
-
)
-
1433
altered_table_name = "a#{table_name}"
-
-
1433
caller = lambda do |definition|
-
1433
rename = options[:rename] || {}
-
1433
foreign_keys.each do |fk|
-
28
if column = rename[fk.options[:column]]
-
6
fk.options[:column] = column
-
end
-
28
to_table = strip_table_name_prefix_and_suffix(fk.to_table)
-
28
definition.foreign_key(to_table, **fk.options)
-
end
-
-
1433
check_constraints.each do |chk|
-
4
definition.check_constraint(chk.expression, **chk.options)
-
end
-
-
1433
yield definition if block_given?
-
end
-
-
1433
transaction do
-
1433
disable_referential_integrity do
-
1433
move_table(table_name, altered_table_name, options.merge(temporary: true))
-
1433
move_table(altered_table_name, table_name, &caller)
-
end
-
end
-
end
-
-
3
def move_table(from, to, options = {}, &block)
-
2866
copy_table(from, to, options, &block)
-
2862
drop_table(from)
-
end
-
-
3
def copy_table(from, to, options = {})
-
2884
from_primary_key = primary_key(from)
-
2884
options[:id] = false
-
2884
create_table(to, **options) do |definition|
-
2884
@definition = definition
-
2884
if from_primary_key.is_a?(Array)
-
4
@definition.primary_keys from_primary_key
-
end
-
-
2884
columns(from).each do |column|
-
34736
column_name = options[:rename] ?
-
142
(options[:rename][column.name] ||
-
options[:rename][column.name.to_sym] ||
-
column.name) : column.name
-
-
34736
@definition.column(column_name, column.type,
-
limit: column.limit, default: column.default,
-
precision: column.precision, scale: column.scale,
-
null: column.null, collation: column.collation,
-
primary_key: column_name == from_primary_key
-
)
-
end
-
-
2884
yield @definition if block_given?
-
end
-
2880
copy_table_indexes(from, to, options[:rename] || {})
-
2880
copy_table_contents(from, to,
-
@definition.columns.map(&:name),
-
options[:rename] || {})
-
end
-
-
3
def copy_table_indexes(from, to, rename = {})
-
2880
indexes(from).each do |index|
-
9046
name = index.name
-
9046
if to == "a#{from}"
-
4522
name = "t#{name}"
-
4524
elsif from == "a#{to}"
-
4518
name = name[1..-1]
-
end
-
-
9046
columns = index.columns
-
9046
if columns.is_a?(Array)
-
9038
to_column_names = columns(to).map(&:name)
-
18166
columns = columns.map { |c| rename[c] || c }.select do |column|
-
9128
to_column_names.include?(column)
-
end
-
end
-
-
9046
unless columns.empty?
-
# index name can't be the same
-
9012
options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
-
9012
options[:unique] = true if index.unique
-
9012
options[:where] = index.where if index.where
-
9012
add_index(to, columns, **options)
-
end
-
end
-
end
-
-
3
def copy_table_contents(from, to, columns, rename = {})
-
37416
column_mappings = Hash[columns.map { |name| [name, name] }]
-
2918
rename.each { |a| column_mappings[a.last] = a.first }
-
2880
from_columns = columns(from).collect(&:name)
-
37416
columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
-
37400
from_columns_to_copy = columns.map { |col| column_mappings[col] }
-
37400
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
-
37400
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
-
-
2880
exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
-
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
-
end
-
-
3
def translate_exception(exception, message:, sql:, binds:)
-
1037
case exception.message
-
# SQLite 3.8.2 returns a newly formatted error message:
-
# UNIQUE constraint failed: *table_name*.*column_name*
-
# Older versions of SQLite return:
-
# column *column_name* is not unique
-
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
-
28
RecordNotUnique.new(message, sql: sql, binds: binds)
-
when /.* may not be NULL/, /NOT NULL constraint failed: .*/
-
10
NotNullViolation.new(message, sql: sql, binds: binds)
-
when /FOREIGN KEY constraint failed/i
-
6
InvalidForeignKey.new(message, sql: sql, binds: binds)
-
else
-
993
super
-
end
-
end
-
-
3
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
-
-
3
def table_structure_with_collation(table_name, basic_structure)
-
28820
collation_hash = {}
-
28820
sql = <<~SQL
-
SELECT sql FROM
-
(SELECT * FROM sqlite_master UNION ALL
-
SELECT * FROM sqlite_temp_master)
-
WHERE type = 'table' AND name = #{quote(table_name)}
-
SQL
-
-
# Result will have following sample string
-
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-
# "password_digest" varchar COLLATE "NOCASE");
-
28820
result = query_value(sql, "SCHEMA")
-
-
28820
if result
-
# Splitting with left parentheses and discarding the first part will return all
-
# columns separated with comma(,).
-
28812
columns_string = result.split("(", 2).last
-
-
28812
columns_string.split(",").each do |column_string|
-
# This regex will match the column name and collation type and will save
-
# the value in $1 and $2 respectively.
-
282249
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
-
end
-
-
28812
basic_structure.map do |column|
-
280610
column_name = column["name"]
-
-
280610
if collation_hash.has_key? column_name
-
82
column["collation"] = collation_hash[column_name]
-
end
-
-
280610
column
-
end
-
else
-
8
basic_structure.to_a
-
end
-
end
-
-
3
def arel_visitor
-
601
Arel::Visitors::SQLite.new(self)
-
end
-
-
3
def build_statement_pool
-
601
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
-
end
-
-
3
def connect
-
2
@connection = ::SQLite3::Database.new(
-
@config[:database].to_s,
-
@config.merge(results_as_hash: true)
-
)
-
2
configure_connection
-
end
-
-
3
def configure_connection
-
603
@connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
-
-
601
execute("PRAGMA foreign_keys = ON", "SCHEMA")
-
end
-
-
3
class SQLite3Integer < Type::Integer # :nodoc:
-
3
private
-
3
def _limit
-
# INTEGER storage class can be stored 8 bytes value.
-
# See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
-
340
limit || 8
-
end
-
end
-
-
3
ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
-
end
-
3
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionAdapters
-
3
class StatementPool # :nodoc:
-
3
include Enumerable
-
-
3
DEFAULT_STATEMENT_LIMIT = 1000
-
-
3
def initialize(statement_limit = nil)
-
1649
@cache = Hash.new { |h, pid| h[pid] = {} }
-
1013
@statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
-
end
-
-
3
def each(&block)
-
cache.each(&block)
-
end
-
-
3
def key?(key)
-
8717
cache.key?(key)
-
end
-
-
3
def [](key)
-
26260
cache[key]
-
end
-
-
3
def length
-
cache.length
-
end
-
-
3
def []=(sql, stmt)
-
5555
while @statement_limit <= cache.size
-
dealloc(cache.shift.last)
-
end
-
5555
cache[sql] = stmt
-
end
-
-
3
def clear
-
3505
cache.each_value do |stmt|
-
5510
dealloc stmt
-
end
-
3505
cache.clear
-
end
-
-
3
def delete(key)
-
dealloc cache[key]
-
cache.delete(key)
-
end
-
-
3
private
-
3
def cache
-
53130
@cache[Process.pid]
-
end
-
-
3
def dealloc(stmt)
-
raise NotImplementedError
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ConnectionHandling
-
2630
RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
-
2630
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
-
-
# Establishes the connection to the database. Accepts a hash as input where
-
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
-
# example for regular databases (MySQL, PostgreSQL, etc):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# adapter: "mysql2",
-
# host: "localhost",
-
# username: "myuser",
-
# password: "mypass",
-
# database: "somedatabase"
-
# )
-
#
-
# Example for SQLite database:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# adapter: "sqlite3",
-
# database: "path/to/dbfile"
-
# )
-
#
-
# Also accepts keys as strings (for parsing from YAML for example):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "adapter" => "sqlite3",
-
# "database" => "path/to/dbfile"
-
# )
-
#
-
# Or a URL:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "postgres://myuser:mypass@localhost/somedatabase"
-
# )
-
#
-
# In case {ActiveRecord::Base.configurations}[rdoc-ref:Core.configurations]
-
# is set (Rails automatically loads the contents of config/database.yml into it),
-
# a symbol can also be given as argument, representing a key in the
-
# configuration hash:
-
#
-
# ActiveRecord::Base.establish_connection(:production)
-
#
-
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
-
# may be returned on an error.
-
3
def establish_connection(config_or_env = nil)
-
273
config_or_env ||= DEFAULT_ENV.call.to_sym
-
273
db_config, owner_name = resolve_config_for_connection(config_or_env)
-
270
connection_handler.establish_connection(db_config, owner_name: owner_name, shard: current_shard)
-
end
-
-
# Connects a model to the databases specified. The +database+ keyword
-
# takes a hash consisting of a +role+ and a +database_key+.
-
#
-
# This will create a connection handler for switching between connections,
-
# look up the config hash using the +database_key+ and finally
-
# establishes a connection to that config.
-
#
-
# class AnimalsModel < ApplicationRecord
-
# self.abstract_class = true
-
#
-
# connects_to database: { writing: :primary, reading: :primary_replica }
-
# end
-
#
-
# +connects_to+ also supports horizontal sharding. The horizontal sharding API
-
# also supports read replicas. Connect a model to a list of shards like this:
-
#
-
# class AnimalsModel < ApplicationRecord
-
# self.abstract_class = true
-
#
-
# connects_to shards: {
-
# default: { writing: :primary, reading: :primary_replica },
-
# shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two }
-
# }
-
# end
-
#
-
# Returns an array of database connections.
-
3
def connects_to(database: {}, shards: {})
-
57
if database.present? && shards.present?
-
2
raise ArgumentError, "connects_to can only accept a `database` or `shards` argument, but not both arguments."
-
end
-
-
55
connections = []
-
-
55
database.each do |role, database_key|
-
50
db_config, owner_name = resolve_config_for_connection(database_key)
-
50
handler = lookup_connection_handler(role.to_sym)
-
-
50
connections << handler.establish_connection(db_config, owner_name: owner_name)
-
end
-
-
55
shards.each do |shard, database_keys|
-
42
database_keys.each do |role, database_key|
-
62
db_config, owner_name = resolve_config_for_connection(database_key)
-
62
handler = lookup_connection_handler(role.to_sym)
-
-
62
connections << handler.establish_connection(db_config, owner_name: owner_name, shard: shard.to_sym)
-
end
-
end
-
-
55
connections
-
end
-
-
# Connects to a role (ex writing, reading or a custom role) and/or
-
# shard for the duration of the block. At the end of the block the
-
# connection will be returned to the original role / shard.
-
#
-
# If only a role is passed, Active Record will look up the connection
-
# based on the requested role. If a non-established role is requested
-
# an `ActiveRecord::ConnectionNotEstablished` error will be raised:
-
#
-
# ActiveRecord::Base.connected_to(role: :writing) do
-
# Dog.create! # creates dog using dog writing connection
-
# end
-
#
-
# ActiveRecord::Base.connected_to(role: :reading) do
-
# Dog.create! # throws exception because we're on a replica
-
# end
-
#
-
# If only a shard is passed, Active Record will look up the shard on the
-
# current role. If a non-existent shard is passed, an
-
# `ActiveRecord::ConnectionNotEstablished` error will be raised.
-
#
-
# ActiveRecord::Base.connected_to(shard: :default) do
-
# # Dog.create! # creates dog in shard with the default key
-
# end
-
#
-
# If a shard and role is passed, Active Record will first lookup the role,
-
# and then look up the connection by shard key.
-
#
-
# ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
-
# # Dog.create! # would raise as we're on a readonly connection
-
# end
-
#
-
# The database kwarg is deprecated and will be removed in 6.2.0 without replacement.
-
3
def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk)
-
166
raise NotImplementedError, "connected_to can only be called on ActiveRecord::Base" unless self == Base
-
-
163
if database
-
4
ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.")
-
end
-
-
163
if database && (role || shard)
-
2
raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments."
-
161
elsif database
-
2
if database.is_a?(Hash)
-
2
role, database = database.first
-
2
role = role.to_sym
-
end
-
-
2
db_config, owner_name = resolve_config_for_connection(database)
-
2
handler = lookup_connection_handler(role)
-
-
2
handler.establish_connection(db_config, owner_name: owner_name)
-
-
2
with_handler(role, &blk)
-
159
elsif shard
-
57
with_shard(shard, role || current_role, prevent_writes, &blk)
-
102
elsif role
-
98
with_role(role, prevent_writes, &blk)
-
else
-
4
raise ArgumentError, "must provide a `shard` and/or `role`."
-
end
-
end
-
-
# Returns true if role is the current connected role.
-
#
-
# ActiveRecord::Base.connected_to(role: :writing) do
-
# ActiveRecord::Base.connected_to?(role: :writing) #=> true
-
# ActiveRecord::Base.connected_to?(role: :reading) #=> false
-
# end
-
3
def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
-
107
current_role == role.to_sym && current_shard == shard.to_sym
-
end
-
-
# Returns the symbol representing the current connected role.
-
#
-
# ActiveRecord::Base.connected_to(role: :writing) do
-
# ActiveRecord::Base.current_role #=> :writing
-
# end
-
#
-
# ActiveRecord::Base.connected_to(role: :reading) do
-
# ActiveRecord::Base.current_role #=> :reading
-
# end
-
3
def current_role
-
170
connection_handlers.key(connection_handler)
-
end
-
-
3
def lookup_connection_handler(handler_key) # :nodoc:
-
271
handler_key ||= ActiveRecord::Base.writing_role
-
271
connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
-
end
-
-
# Clears the query cache for all connections associated with the current thread.
-
3
def clear_query_caches_for_current_thread
-
132709
ActiveRecord::Base.connection_handlers.each_value do |handler|
-
132721
handler.connection_pool_list.each do |pool|
-
972917
pool.connection.clear_query_cache if pool.active_connection?
-
end
-
end
-
end
-
-
# Returns the connection currently associated with the class. This can
-
# also be used to "borrow" the connection to do database work unrelated
-
# to any of the specific Active Records.
-
3
def connection
-
266512
retrieve_connection
-
end
-
-
3
attr_writer :connection_specification_name
-
-
# Return the connection specification name from the current class or its parent.
-
3
def connection_specification_name
-
556299
if !defined?(@connection_specification_name) || @connection_specification_name.nil?
-
286786
return self == Base ? Base.name : superclass.connection_specification_name
-
end
-
269513
@connection_specification_name
-
end
-
-
3
def primary_class? # :nodoc:
-
384
self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
-
end
-
-
# Returns the configuration of the associated connection as a hash:
-
#
-
# ActiveRecord::Base.connection_config
-
# # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
-
#
-
# Please use only for reading.
-
3
def connection_config
-
connection_pool.db_config.configuration_hash
-
end
-
3
deprecate connection_config: "Use connection_db_config instead"
-
-
# Returns the db_config object from the associated connection:
-
#
-
# ActiveRecord::Base.connection_db_config
-
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
-
# @name="primary", @config={pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}>
-
#
-
# Use only for reading.
-
3
def connection_db_config
-
117
connection_pool.db_config
-
end
-
-
3
def connection_pool
-
1206
connection_handler.retrieve_connection_pool(connection_specification_name, shard: current_shard) || raise(ConnectionNotEstablished)
-
end
-
-
3
def retrieve_connection
-
266686
connection_handler.retrieve_connection(connection_specification_name, shard: current_shard)
-
end
-
-
# Returns +true+ if Active Record is connected.
-
3
def connected?
-
1585
connection_handler.connected?(connection_specification_name, shard: current_shard)
-
end
-
-
3
def remove_connection(name = nil)
-
96
name ||= @connection_specification_name if defined?(@connection_specification_name)
-
# if removing a connection that has a pool, we reset the
-
# connection_specification_name so it will use the parent
-
# pool.
-
96
if connection_handler.retrieve_connection_pool(name, shard: current_shard)
-
93
self.connection_specification_name = nil
-
end
-
-
96
connection_handler.remove_connection_pool(name, shard: current_shard)
-
end
-
-
3
def clear_cache! # :nodoc:
-
97
connection.schema_cache.clear!
-
end
-
-
3
delegate :clear_active_connections!, :clear_reloadable_connections!,
-
:clear_all_connections!, :flush_idle_connections!, to: :connection_handler
-
-
3
private
-
3
def resolve_config_for_connection(config_or_env)
-
387
raise "Anonymous class is not allowed." unless name
-
-
384
owner_name = primary_class? ? Base.name : name
-
384
self.connection_specification_name = owner_name
-
-
384
db_config = Base.configurations.resolve(config_or_env)
-
384
[db_config, owner_name]
-
end
-
-
3
def with_handler(handler_key, &blk)
-
157
handler = lookup_connection_handler(handler_key)
-
157
swap_connection_handler(handler, &blk)
-
end
-
-
3
def with_role(role, prevent_writes, &blk)
-
155
prevent_writes = true if role == reading_role
-
-
155
with_handler(role.to_sym) do
-
155
connection_handler.while_preventing_writes(prevent_writes, &blk)
-
end
-
end
-
-
3
def with_shard(shard, role, prevent_writes)
-
57
old_shard = current_shard
-
-
57
with_role(role, prevent_writes) do
-
57
self.current_shard = shard
-
57
yield
-
end
-
ensure
-
57
self.current_shard = old_shard
-
end
-
-
3
def swap_connection_handler(handler, &blk) # :nodoc:
-
157
old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
-
157
return_value = yield
-
145
return_value.load if return_value.is_a? ActiveRecord::Relation
-
145
return_value
-
ensure
-
157
ActiveRecord::Base.connection_handler = old_handler
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
3
require "active_support/core_ext/hash/indifferent_access"
-
3
require "active_support/core_ext/string/filters"
-
3
require "active_support/parameter_filter"
-
3
require "concurrent/map"
-
-
3
module ActiveRecord
-
3
module Core
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
##
-
# :singleton-method:
-
#
-
# Accepts a logger conforming to the interface of Log4r which is then
-
# passed on to any new database connections made and which can be
-
# retrieved on both a class and instance level by calling +logger+.
-
3
mattr_accessor :logger, instance_writer: false
-
-
##
-
# :singleton-method:
-
#
-
# Specifies if the methods calling database queries should be logged below
-
# their relevant queries. Defaults to false.
-
3
mattr_accessor :verbose_query_logs, instance_writer: false, default: false
-
-
##
-
# Contains the database configuration - as is typically stored in config/database.yml -
-
# as an ActiveRecord::DatabaseConfigurations object.
-
#
-
# For example, the following database.yml...
-
#
-
# development:
-
# adapter: sqlite3
-
# database: db/development.sqlite3
-
#
-
# production:
-
# adapter: sqlite3
-
# database: db/production.sqlite3
-
#
-
# ...would result in ActiveRecord::Base.configurations to look like this:
-
#
-
# #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
-
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
-
# @name="primary", @config={adapter: "sqlite3", database: "db/development.sqlite3"}>,
-
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
-
# @name="primary", @config={adapter: "sqlite3", database: "db/production.sqlite3"}>
-
# ]>
-
3
def self.configurations=(config)
-
388
@@configurations = ActiveRecord::DatabaseConfigurations.new(config)
-
end
-
3
self.configurations = {}
-
-
# Returns fully resolved ActiveRecord::DatabaseConfigurations object
-
3
def self.configurations
-
2063
@@configurations
-
end
-
-
##
-
# :singleton-method:
-
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
-
# dates and times from the database. This is set to :utc by default.
-
3
mattr_accessor :default_timezone, instance_writer: false, default: :utc
-
-
##
-
# :singleton-method:
-
# Specifies the format to use when dumping the database schema with Rails'
-
# Rakefile. If :sql, the schema is dumped as (potentially database-
-
# specific) SQL statements. If :ruby, the schema is dumped as an
-
# ActiveRecord::Schema file which can be loaded into any database that
-
# supports migrations. Use :ruby if you want to have different database
-
# adapters for, e.g., your development and test environments.
-
3
mattr_accessor :schema_format, instance_writer: false, default: :ruby
-
-
##
-
# :singleton-method:
-
# Specifies if an error should be raised if the query has an order being
-
# ignored when doing batch queries. Useful in applications where the
-
# scope being ignored is error-worthy, rather than a warning.
-
3
mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
-
-
# :singleton-method:
-
# Specify the behavior for unsafe raw query methods. Values are as follows
-
# deprecated - Warnings are logged when unsafe raw SQL is passed to
-
# query methods.
-
# disabled - Unsafe raw SQL passed to query methods results in
-
# UnknownAttributeReference exception.
-
3
mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
-
-
##
-
# :singleton-method:
-
# Specify whether or not to use timestamps for migration versions
-
3
mattr_accessor :timestamped_migrations, instance_writer: false, default: true
-
-
##
-
# :singleton-method:
-
# Specify whether schema dump should happen at the end of the
-
# db:migrate rails command. This is true by default, which is useful for the
-
# development environment. This should ideally be false in the production
-
# environment where dumping schema is rarely needed.
-
3
mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
-
-
##
-
# :singleton-method:
-
# Specifies which database schemas to dump when calling db:schema:dump.
-
# If the value is :schema_search_path (the default), any schemas listed in
-
# schema_search_path are dumped. Use :all to dump all schemas regardless
-
# of schema_search_path, or a string of comma separated schemas for a
-
# custom list.
-
3
mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
-
-
##
-
# :singleton-method:
-
# Specify a threshold for the size of query result sets. If the number of
-
# records in the set exceeds the threshold, a warning is logged. This can
-
# be used to identify queries which load thousands of records and
-
# potentially cause memory bloat.
-
3
mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
-
-
3
mattr_accessor :maintain_test_schema, instance_accessor: false
-
-
3
class_attribute :belongs_to_required_by_default, instance_accessor: false
-
-
3
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
-
-
3
mattr_accessor :connection_handlers, instance_accessor: false, default: {}
-
-
3
mattr_accessor :writing_role, instance_accessor: false, default: :writing
-
-
3
mattr_accessor :reading_role, instance_accessor: false, default: :reading
-
-
3
mattr_accessor :has_many_inversing, instance_accessor: false, default: false
-
-
3
class_attribute :default_connection_handler, instance_writer: false
-
-
3
class_attribute :default_shard, instance_writer: false
-
-
3
self.filter_attributes = []
-
-
3
def self.connection_handler
-
597752
Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
-
end
-
-
3
def self.connection_handler=(handler)
-
331
Thread.current.thread_variable_set(:ar_connection_handler, handler)
-
end
-
-
3
def self.current_shard
-
270083
Thread.current.thread_variable_get(:ar_shard) || default_shard
-
end
-
-
3
def self.current_shard=(shard)
-
114
Thread.current.thread_variable_set(:ar_shard, shard)
-
end
-
-
3
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
-
3
self.default_shard = :default
-
end
-
-
3
module ClassMethods
-
3
def initialize_find_by_cache # :nodoc:
-
5033
@find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
-
end
-
-
3
def inherited(child_class) # :nodoc:
-
# initialize cache at class definition for thread safety
-
2940
child_class.initialize_find_by_cache
-
2940
unless child_class.base_class?
-
628
klass = self
-
628
until klass.base_class?
-
69
klass.initialize_find_by_cache
-
69
klass = klass.superclass
-
end
-
end
-
2940
super
-
end
-
-
3
def find(*ids) # :nodoc:
-
# We don't have cache keys for this stuff yet
-
14129
return super unless ids.length == 1
-
13798
return super if block_given? || primary_key.nil? || scope_attributes?
-
-
2612
id = ids.first
-
-
2612
return super if StatementCache.unsupported_value?(id)
-
-
2552
key = primary_key
-
-
2552
statement = cached_find_by_statement(key) { |params|
-
401
where(key => params.bind).limit(1)
-
}
-
-
2552
statement.execute([id], connection).first ||
-
raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
-
end
-
-
3
def find_by(*args) # :nodoc:
-
554
return super if scope_attributes?
-
-
464
hash = args.first
-
464
return super unless Hash === hash
-
-
965
values = hash.values.map! { |value| value.is_a?(Base) ? value.id : value }
-
962
return super if values.any? { |v| StatementCache.unsupported_value?(v) }
-
-
422
keys = hash.keys.map! do |key|
-
483
attribute_aliases[name = key.to_s] || begin
-
480
reflection = _reflect_on_association(name)
-
480
if reflection&.belongs_to? && !reflection.polymorphic?
-
6
reflection.join_foreign_key
-
474
elsif reflect_on_aggregation(name)
-
18
return super
-
else
-
456
name
-
end
-
end
-
end
-
-
869
return super unless keys.all? { |k| columns_hash.key?(k) }
-
-
392
statement = cached_find_by_statement(keys) { |params|
-
331
wheres = keys.index_with { params.bind }
-
161
where(wheres).limit(1)
-
}
-
-
392
begin
-
392
statement.execute(values, connection).first
-
3
rescue TypeError
-
raise ActiveRecord::StatementInvalid
-
end
-
end
-
-
3
def find_by!(*args) # :nodoc:
-
48
find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name))
-
end
-
-
3
def initialize_generated_modules # :nodoc:
-
2943
generated_association_methods
-
end
-
-
3
def generated_association_methods # :nodoc:
-
8316
@generated_association_methods ||= begin
-
2943
mod = const_set(:GeneratedAssociationMethods, Module.new)
-
2943
private_constant :GeneratedAssociationMethods
-
2943
include mod
-
-
2943
mod
-
end
-
end
-
-
# Returns columns which shouldn't be exposed while calling +#inspect+.
-
3
def filter_attributes
-
559
if defined?(@filter_attributes)
-
278
@filter_attributes
-
else
-
281
superclass.filter_attributes
-
end
-
end
-
-
# Specifies columns which shouldn't be exposed while calling +#inspect+.
-
3
attr_writer :filter_attributes
-
-
# Returns a string like 'Post(id:integer, title:string, body:text)'
-
3
def inspect # :nodoc:
-
21
if self == Base
-
3
super
-
18
elsif abstract_class?
-
3
"#{super}(abstract)"
-
15
elsif !connected?
-
"#{super} (call '#{super}.connection' to establish a connection)"
-
15
elsif table_exists?
-
120
attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
-
12
"#{super}(#{attr_list})"
-
else
-
3
"#{super}(Table doesn't exist)"
-
end
-
end
-
-
# Overwrite the default class equality method to provide support for decorated models.
-
3
def ===(object) # :nodoc:
-
16811
object.is_a?(self)
-
end
-
-
# Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
-
#
-
# class Post < ActiveRecord::Base
-
# scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
-
# end
-
3
def arel_table # :nodoc:
-
210653
@arel_table ||= Arel::Table.new(table_name, klass: self)
-
end
-
-
3
def arel_attribute(name, table = arel_table) # :nodoc:
-
3
table[name]
-
end
-
3
deprecate :arel_attribute
-
-
3
def predicate_builder # :nodoc:
-
171269
@predicate_builder ||= PredicateBuilder.new(table_metadata)
-
end
-
-
3
def type_caster # :nodoc:
-
2548
TypeCaster::Map.new(self)
-
end
-
-
3
def _internal? # :nodoc:
-
false
-
end
-
-
3
def cached_find_by_statement(key, &block) # :nodoc:
-
6174
cache = @find_by_statement_cache[connection.prepared_statements]
-
7436
cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
-
end
-
-
3
private
-
3
def relation
-
85519
relation = Relation.create(self)
-
-
85519
if finder_needs_type_condition? && !ignore_default_scope?
-
7281
relation.where!(type_condition)
-
else
-
78238
relation
-
end
-
end
-
-
3
def table_metadata
-
1882
TableMetadata.new(self, arel_table)
-
end
-
end
-
-
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
-
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
-
# In both instances, valid attribute keys are determined by the column names of the associated table --
-
# hence you can't have attributes that aren't part of the table columns.
-
#
-
# ==== Example:
-
# # Instantiates a single new object
-
# User.new(first_name: 'Jamie')
-
3
def initialize(attributes = nil)
-
15758
@new_record = true
-
15758
@attributes = self.class._default_attributes.deep_dup
-
-
15758
init_internals
-
15758
initialize_internals_callback
-
-
15758
assign_attributes(attributes) if attributes
-
-
15713
yield self if block_given?
-
15710
_run_initialize_callbacks
-
end
-
-
# Initialize an empty model object from +coder+. +coder+ should be
-
# the result of previously encoding an Active Record model, using
-
# #encode_with.
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
#
-
# old_post = Post.new(title: "hello world")
-
# coder = {}
-
# old_post.encode_with(coder)
-
#
-
# post = Post.allocate
-
# post.init_with(coder)
-
# post.title # => 'hello world'
-
3
def init_with(coder, &block)
-
68
coder = LegacyYamlAdapter.convert(self.class, coder)
-
68
attributes = self.class.yaml_encoder.decode(coder)
-
68
init_with_attributes(attributes, coder["new_record"], &block)
-
end
-
-
##
-
# Initialize an empty model object from +attributes+.
-
# +attributes+ should be an attributes object, and unlike the
-
# `initialize` method, no assignment calls are made per attribute.
-
3
def init_with_attributes(attributes, new_record = false) # :nodoc:
-
246100
@new_record = new_record
-
246100
@attributes = attributes
-
-
246100
init_internals
-
-
246100
yield self if block_given?
-
-
246100
_run_find_callbacks
-
246100
_run_initialize_callbacks
-
-
246100
self
-
end
-
-
##
-
# :method: clone
-
# Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
-
# That means that modifying attributes of the clone will modify the original, since they will both point to the
-
# same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
-
#
-
# user = User.first
-
# new_user = user.clone
-
# user.name # => "Bob"
-
# new_user.name = "Joe"
-
# user.name # => "Joe"
-
#
-
# user.object_id == new_user.object_id # => false
-
# user.name.object_id == new_user.name.object_id # => true
-
#
-
# user.name.object_id == user.dup.name.object_id # => false
-
-
##
-
# :method: dup
-
# Duped objects have no id assigned and are treated as new records. Note
-
# that this is a "shallow" copy as it copies the object's attributes
-
# only, not its associations. The extent of a "deep" copy is application
-
# specific and is therefore left to the application to implement according
-
# to its need.
-
# The dup method does not preserve the timestamps (created|updated)_(at|on).
-
-
##
-
3
def initialize_dup(other) # :nodoc:
-
111
@attributes = @attributes.deep_dup
-
111
@attributes.reset(@primary_key)
-
-
111
_run_initialize_callbacks
-
-
111
@new_record = true
-
111
@previously_new_record = false
-
111
@destroyed = false
-
111
@_start_transaction_state = nil
-
-
111
super
-
end
-
-
# Populate +coder+ with attributes about this record that should be
-
# serialized. The structure of +coder+ defined in this method is
-
# guaranteed to match the structure of +coder+ passed to the #init_with
-
# method.
-
#
-
# Example:
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
# coder = {}
-
# Post.new.encode_with(coder)
-
# coder # => {"attributes" => {"id" => nil, ... }}
-
3
def encode_with(coder)
-
77
self.class.yaml_encoder.encode(@attributes, coder)
-
77
coder["new_record"] = new_record?
-
77
coder["active_record_yaml_version"] = 2
-
end
-
-
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
-
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
-
#
-
# Note that new records are different from any other record by definition, unless the
-
# other record is the receiver itself. Besides, if you fetch existing records with
-
# +select+ and leave the ID out, you're on your own, this predicate will return false.
-
#
-
# Note also that destroying a record preserves its ID in the model instance, so deleted
-
# models are still comparable.
-
3
def ==(comparison_object)
-
10382
super ||
-
comparison_object.instance_of?(self.class) &&
-
!id.nil? &&
-
comparison_object.id == id
-
end
-
3
alias :eql? :==
-
-
# Delegates to id in order to allow two records of the same type and id to work with something like:
-
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
-
3
def hash
-
35231
if id
-
31629
self.class.hash ^ id.hash
-
else
-
3602
super
-
end
-
end
-
-
# Clone and freeze the attributes hash such that associations are still
-
# accessible, even on destroyed records, but cloned models will not be
-
# frozen.
-
3
def freeze
-
1294
@attributes = @attributes.clone.freeze
-
1294
self
-
end
-
-
# Returns +true+ if the attributes hash has been frozen.
-
3
def frozen?
-
17482
@attributes.frozen?
-
end
-
-
# Allows sort on objects
-
3
def <=>(other_object)
-
53
if other_object.is_a?(self.class)
-
41
to_key <=> other_object.to_key
-
else
-
12
super
-
end
-
end
-
-
3
def present? # :nodoc:
-
75
true
-
end
-
-
3
def blank? # :nodoc:
-
78
false
-
end
-
-
# Returns +true+ if the record is read only.
-
3
def readonly?
-
17591
@readonly
-
end
-
-
# Returns +true+ if the record is in strict_loading mode.
-
3
def strict_loading?
-
5563
@strict_loading
-
end
-
-
# Sets the record to strict_loading mode. This will raise an error
-
# if the record tries to lazily load an association.
-
#
-
# user = User.first
-
# user.strict_loading!
-
# user.comments.to_a
-
# => ActiveRecord::StrictLoadingViolationError
-
3
def strict_loading!
-
75
@strict_loading = true
-
end
-
-
# Marks this record as read only.
-
3
def readonly!
-
381
@readonly = true
-
end
-
-
3
def connection_handler
-
self.class.connection_handler
-
end
-
-
# Returns the contents of the record as a nicely formatted string.
-
3
def inspect
-
# We check defined?(@attributes) not to issue warnings if the object is
-
# allocated but not initialized.
-
268
inspection = if defined?(@attributes) && @attributes
-
self.class.attribute_names.collect do |name|
-
2700
if _has_attribute?(name)
-
2601
attr = _read_attribute(name)
-
2601
value = if attr.nil?
-
380
attr.inspect
-
else
-
2221
attr = format_for_inspect(attr)
-
2221
inspection_filter.filter_param(name, attr)
-
end
-
2601
"#{name}: #{value}"
-
end
-
265
end.compact.join(", ")
-
else
-
3
"not initialized"
-
end
-
-
268
"#<#{self.class} #{inspection}>"
-
end
-
-
# Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
-
# when pp is required.
-
3
def pretty_print(pp)
-
31
return super if custom_inspect_method_defined?
-
28
pp.object_address_group(self) do
-
28
if defined?(@attributes) && @attributes
-
286
attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
-
261
pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
-
261
pp.breakable " "
-
261
pp.group(1) do
-
261
pp.text attr_name
-
261
pp.text ":"
-
261
pp.breakable
-
261
value = _read_attribute(attr_name)
-
261
value = inspection_filter.filter_param(attr_name, value) unless value.nil?
-
261
pp.pp value
-
end
-
end
-
else
-
3
pp.breakable " "
-
3
pp.text "not initialized"
-
end
-
end
-
end
-
-
# Returns a hash of the given methods with their names as keys and returned values as values.
-
3
def slice(*methods)
-
24
Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
-
end
-
-
3
private
-
# +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
-
# the array, and then rescues from the possible +NoMethodError+. If those elements are
-
# +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
-
# which significantly impacts upon performance.
-
#
-
# So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here.
-
#
-
# See also https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
-
3
def to_ary
-
nil
-
end
-
-
3
def init_internals
-
261858
@primary_key = self.class.primary_key
-
261858
@readonly = false
-
261858
@previously_new_record = false
-
261858
@destroyed = false
-
261858
@marked_for_destruction = false
-
261858
@destroyed_by_association = nil
-
261858
@_start_transaction_state = nil
-
261858
@strict_loading = self.class.strict_loading_by_default
-
-
261858
self.class.define_attribute_methods
-
end
-
-
3
def initialize_internals_callback
-
end
-
-
3
def custom_inspect_method_defined?
-
31
self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
-
end
-
-
3
class InspectionMask < DelegateClass(::String)
-
3
def pretty_print(pp)
-
6
pp.text __getobj__
-
end
-
end
-
3
private_constant :InspectionMask
-
-
3
def inspection_filter
-
2388
@inspection_filter ||= begin
-
248
mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
-
248
ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record Counter Cache
-
3
module CounterCache
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
# Resets one or more counter caches to their correct value using an SQL
-
# count query. This is useful when adding new counter caches, or if the
-
# counter has been corrupted or modified directly by SQL.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to reset a counter on.
-
# * +counters+ - One or more association counters to reset. Association name or counter name can be given.
-
# * <tt>:touch</tt> - Touch timestamp columns when updating.
-
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
-
# touch that column or an array of symbols to touch just those ones.
-
#
-
# ==== Examples
-
#
-
# # For the Post with id #1, reset the comments_count
-
# Post.reset_counters(1, :comments)
-
#
-
# # Like above, but also touch the +updated_at+ and/or +updated_on+
-
# # attributes.
-
# Post.reset_counters(1, :comments, touch: true)
-
3
def reset_counters(id, *counters, touch: nil)
-
60
object = find(id)
-
-
60
counters.each do |counter_association|
-
72
has_many_association = _reflect_on_association(counter_association)
-
72
unless has_many_association
-
6
has_many = reflect_on_all_associations(:has_many)
-
24
has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
-
6
counter_association = has_many_association.plural_name if has_many_association
-
end
-
72
raise ArgumentError, "'#{name}' has no association called '#{counter_association}'" unless has_many_association
-
-
69
if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
-
6
has_many_association = has_many_association.through_reflection
-
end
-
-
69
foreign_key = has_many_association.foreign_key.to_s
-
69
child_class = has_many_association.klass
-
324
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
-
69
counter_name = reflection.counter_cache_column
-
-
69
updates = { counter_name => object.send(counter_association).count(:all) }
-
-
69
if touch
-
27
names = touch if touch != true
-
27
names = Array.wrap(names)
-
27
options = names.extract_options!
-
27
touch_updates = touch_attributes_with_time(*names, **options)
-
27
updates.merge!(touch_updates)
-
end
-
-
69
unscoped.where(primary_key => object.id).update_all(updates)
-
end
-
-
57
true
-
end
-
-
# A generic "counter updater" implementation, intended primarily to be
-
# used by #increment_counter and #decrement_counter, but which may also
-
# be useful on its own. It simply does a direct SQL update for the record
-
# with the given ID, altering the given hash of counters by the amount
-
# given by the corresponding value:
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to update a counter on or an array of ids.
-
# * +counters+ - A Hash containing the names of the fields
-
# to update as keys and the amount to update the field by as values.
-
# * <tt>:touch</tt> option - Touch timestamp columns when updating.
-
# If attribute names are passed, they are updated along with updated_at/on
-
# attributes.
-
#
-
# ==== Examples
-
#
-
# # For the Post with id of 5, decrement the comment_count by 1, and
-
# # increment the action_count by 1
-
# Post.update_counters 5, comment_count: -1, action_count: 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) - 1,
-
# # action_count = COALESCE(action_count, 0) + 1
-
# # WHERE id = 5
-
#
-
# # For the Posts with id of 10 and 15, increment the comment_count by 1
-
# Post.update_counters [10, 15], comment_count: 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) + 1
-
# # WHERE id IN (10, 15)
-
#
-
# # For the Posts with id of 10 and 15, increment the comment_count by 1
-
# # and update the updated_at value for each counter.
-
# Post.update_counters [10, 15], comment_count: 1, touch: true
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) + 1,
-
# # `updated_at` = '2016-10-13T09:59:23-05:00'
-
# # WHERE id IN (10, 15)
-
3
def update_counters(id, counters)
-
618
unscoped.where!(primary_key => id).update_counters(counters)
-
end
-
-
# Increment a numeric field by one, via a direct SQL update.
-
#
-
# This method is used primarily for maintaining counter_cache columns that are
-
# used to store aggregate values. For example, a +DiscussionBoard+ may cache
-
# posts_count and comments_count to avoid running an SQL query to calculate the
-
# number of posts and comments there are, each time it is displayed.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be incremented.
-
# * +id+ - The id of the object that should be incremented or an array of ids.
-
# * <tt>:touch</tt> - Touch timestamp columns when updating.
-
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
-
# touch that column or an array of symbols to touch just those ones.
-
#
-
# ==== Examples
-
#
-
# # Increment the posts_count column for the record with an id of 5
-
# DiscussionBoard.increment_counter(:posts_count, 5)
-
#
-
# # Increment the posts_count column for the record with an id of 5
-
# # and update the updated_at value.
-
# DiscussionBoard.increment_counter(:posts_count, 5, touch: true)
-
3
def increment_counter(counter_name, id, touch: nil)
-
42
update_counters(id, counter_name => 1, touch: touch)
-
end
-
-
# Decrement a numeric field by one, via a direct SQL update.
-
#
-
# This works the same as #increment_counter but reduces the column value by
-
# 1 instead of increasing it.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be decremented.
-
# * +id+ - The id of the object that should be decremented or an array of ids.
-
# * <tt>:touch</tt> - Touch timestamp columns when updating.
-
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
-
# touch that column or an array of symbols to touch just those ones.
-
#
-
# ==== Examples
-
#
-
# # Decrement the posts_count column for the record with an id of 5
-
# DiscussionBoard.decrement_counter(:posts_count, 5)
-
#
-
# # Decrement the posts_count column for the record with an id of 5
-
# # and update the updated_at value.
-
# DiscussionBoard.decrement_counter(:posts_count, 5, touch: true)
-
3
def decrement_counter(counter_name, id, touch: nil)
-
30
update_counters(id, counter_name => -1, touch: touch)
-
end
-
end
-
-
3
private
-
3
def _create_record(attribute_names = self.attribute_names)
-
12437
id = super
-
-
12384
each_counter_cached_associations do |association|
-
836
association.increment_counters
-
end
-
-
12384
id
-
end
-
-
3
def destroy_row
-
886
affected_rows = super
-
-
883
if affected_rows > 0
-
871
each_counter_cached_associations do |association|
-
182
foreign_key = association.reflection.foreign_key.to_sym
-
182
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
-
131
association.decrement_counters
-
end
-
end
-
end
-
-
883
affected_rows
-
end
-
-
3
def each_counter_cached_associations
-
13255
_reflections.each do |name, reflection|
-
132346
yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/database_configurations/database_config"
-
3
require "active_record/database_configurations/hash_config"
-
3
require "active_record/database_configurations/url_config"
-
3
require "active_record/database_configurations/connection_url_resolver"
-
-
3
module ActiveRecord
-
# ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
-
# objects (either a HashConfig or UrlConfig) that are constructed from the
-
# application's database configuration hash or URL string.
-
3
class DatabaseConfigurations
-
3
class InvalidConfigurationError < StandardError; end
-
-
3
attr_reader :configurations
-
3
delegate :any?, to: :configurations
-
-
3
def initialize(configurations = {})
-
538
@configurations = build_configs(configurations)
-
end
-
-
# Collects the configs for the environment and optionally the specification
-
# name passed in. To include replica configurations pass <tt>include_replicas: true</tt>.
-
#
-
# If a name is provided a single DatabaseConfig object will be
-
# returned, otherwise an array of DatabaseConfig objects will be
-
# returned that corresponds with the environment and type requested.
-
#
-
# ==== Options
-
#
-
# * <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
-
# configs for all environments.
-
# * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
-
# to +nil+. If no +env_name+ is specified the config for the default env and the
-
# passed +name+ will be returned.
-
# * <tt>include_replicas:</tt> Determines whether to include replicas in
-
# the returned list. Most of the time we're only iterating over the write
-
# connection (i.e. migrations don't need to run for the write and read connection).
-
# Defaults to +false+.
-
3
def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false)
-
613
if spec_name
-
3
name = spec_name
-
3
ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 6.2")
-
end
-
-
613
env_name ||= default_env if name
-
613
configs = env_with_configs(env_name)
-
-
613
unless include_replicas
-
610
configs = configs.select do |db_config|
-
768
!db_config.replica?
-
end
-
end
-
-
613
if name
-
443
configs.find do |db_config|
-
494
db_config.name == name
-
end
-
else
-
170
configs
-
end
-
end
-
-
# Returns the config hash that corresponds with the environment
-
#
-
# If the application has multiple databases +default_hash+ will
-
# return the first config hash for the environment.
-
#
-
# { database: "my_db", adapter: "mysql2" }
-
3
def default_hash(env = default_env)
-
3
default = find_db_config(env)
-
3
default.configuration_hash if default
-
end
-
3
alias :[] :default_hash
-
3
deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
-
-
# Returns a single DatabaseConfig object based on the requested environment.
-
#
-
# If the application has multiple databases +find_db_config+ will return
-
# the first DatabaseConfig for the environment.
-
3
def find_db_config(env)
-
configurations
-
765
.sort_by { |db_config| db_config.for_current_env? ? 0 : 1 }
-
281
.find do |db_config|
-
396
db_config.env_name == env.to_s ||
-
183
(db_config.for_current_env? && db_config.name == env.to_s)
-
end
-
end
-
-
# Returns the DatabaseConfigurations object as a Hash.
-
3
def to_h
-
12
configurations.inject({}) do |memo, db_config|
-
36
memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
-
end
-
end
-
3
deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
-
-
# Checks if the application's configurations are empty.
-
#
-
# Aliased to blank?
-
3
def empty?
-
6
configurations.empty?
-
end
-
3
alias :blank? :empty?
-
-
3
def each
-
3
throw_getter_deprecation(:each)
-
3
configurations.each { |config|
-
9
yield [config.env_name, config.configuration_hash]
-
}
-
end
-
-
3
def first
-
3
throw_getter_deprecation(:first)
-
3
config = configurations.first
-
3
[config.env_name, config.configuration_hash]
-
end
-
-
# Returns fully resolved connection, accepts hash, string or symbol.
-
# Always returns a DatabaseConfiguration::DatabaseConfig
-
#
-
# == Examples
-
#
-
# Symbol representing current environment.
-
#
-
# DatabaseConfigurations.new("production" => {}).resolve(:production)
-
# # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {})
-
#
-
# One layer deep hash of connection values.
-
#
-
# DatabaseConfigurations.new({}).resolve("adapter" => "sqlite3")
-
# # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"})
-
#
-
# Connection URL.
-
#
-
# DatabaseConfigurations.new({}).resolve("postgresql://localhost/foo")
-
# # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"})
-
3
def resolve(config) # :nodoc:
-
1356
return config if DatabaseConfigurations::DatabaseConfig === config
-
-
528
case config
-
when Symbol
-
272
resolve_symbol_connection(config)
-
when Hash, String
-
253
build_db_config_from_raw_config(default_env, "primary", config)
-
else
-
3
raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config.inspect}"
-
end
-
end
-
-
3
private
-
3
def default_env
-
835
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
-
end
-
-
3
def env_with_configs(env = nil)
-
613
if env
-
2500
configurations.select { |db_config| db_config.env_name == env }
-
else
-
47
configurations
-
end
-
end
-
-
3
def build_configs(configs)
-
540
return configs.configurations if configs.is_a?(DatabaseConfigurations)
-
349
return configs if configs.is_a?(Array)
-
-
347
db_configs = configs.flat_map do |env_name, config|
-
1082
if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
-
187
walk_configs(env_name.to_s, config)
-
else
-
282
build_db_config_from_raw_config(env_name.to_s, "primary", config)
-
end
-
end
-
-
341
unless db_configs.find(&:for_current_env?)
-
232
db_configs << environment_url_config(default_env, "primary", {})
-
end
-
-
341
merge_db_environment_variables(default_env, db_configs.compact)
-
end
-
-
3
def walk_configs(env_name, config)
-
187
config.map do |name, sub_config|
-
343
build_db_config_from_raw_config(env_name, name.to_s, sub_config)
-
end
-
end
-
-
3
def resolve_symbol_connection(name)
-
272
if db_config = find_db_config(name)
-
269
db_config
-
else
-
3
raise AdapterNotSpecified, <<~MSG
-
The `#{name}` database is not configured for the `#{default_env}` environment.
-
-
Available databases configurations are:
-
-
#{build_configuration_sentence}
-
MSG
-
end
-
end
-
-
3
def build_configuration_sentence
-
3
configs = configs_for(include_replicas: true)
-
-
configs.group_by(&:env_name).map do |env, config|
-
6
names = config.map(&:name)
-
6
if names.size > 1
-
"#{env}: #{names.join(", ")}"
-
else
-
6
env
-
end
-
3
end.join("\n")
-
end
-
-
3
def build_db_config_from_raw_config(env_name, name, config)
-
878
case config
-
when String
-
49
build_db_config_from_string(env_name, name, config)
-
when Hash
-
826
build_db_config_from_hash(env_name, name, config.symbolize_keys)
-
else
-
3
raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
-
end
-
end
-
-
3
def build_db_config_from_string(env_name, name, config)
-
49
url = config
-
49
uri = URI.parse(url)
-
49
if uri.scheme
-
43
UrlConfig.new(env_name, name, url)
-
else
-
6
raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
-
end
-
end
-
-
3
def build_db_config_from_hash(env_name, name, config)
-
826
if config.has_key?(:url)
-
129
url = config[:url]
-
129
config_without_url = config.dup
-
129
config_without_url.delete :url
-
-
129
UrlConfig.new(env_name, name, url, config_without_url)
-
else
-
697
HashConfig.new(env_name, name, config)
-
end
-
end
-
-
3
def merge_db_environment_variables(current_env, configs)
-
341
configs.map do |config|
-
658
next config if config.is_a?(UrlConfig) || config.env_name != current_env
-
-
149
url_config = environment_url_config(current_env, config.name, config.configuration_hash)
-
149
url_config || config
-
end
-
end
-
-
3
def environment_url_config(env, name, config)
-
381
url = environment_value_for(name)
-
381
return unless url
-
-
81
UrlConfig.new(env, name, url, config)
-
end
-
-
3
def environment_value_for(name)
-
381
name_env_key = "#{name.upcase}_DATABASE_URL"
-
381
url = ENV[name_env_key]
-
381
url ||= ENV["DATABASE_URL"] if name == "primary"
-
381
url
-
end
-
-
3
def method_missing(method, *args, &blk)
-
14
case method
-
when :fetch
-
3
throw_getter_deprecation(method)
-
3
configs_for(env_name: args.first)
-
when :values
-
6
throw_getter_deprecation(method)
-
6
configurations.map(&:configuration_hash)
-
when :[]=
-
2
throw_setter_deprecation(method)
-
-
2
env_name = args[0]
-
2
config = args[1]
-
-
8
remaining_configs = configurations.reject { |db_config| db_config.env_name == env_name }
-
2
new_config = build_configs(env_name => config)
-
2
new_configs = remaining_configs + new_config
-
-
2
ActiveRecord::Base.configurations = new_configs
-
else
-
3
raise NotImplementedError, "`ActiveRecord::Base.configurations` in Rails 6 now returns an object instead of a hash. The `#{method}` method is not supported. Please use `configs_for` or consult the documentation for supported methods."
-
end
-
end
-
-
3
def throw_setter_deprecation(method)
-
2
ActiveSupport::Deprecation.warn("Setting `ActiveRecord::Base.configurations` with `#{method}` is deprecated. Use `ActiveRecord::Base.configurations=` directly to set the configurations instead.")
-
end
-
-
3
def throw_getter_deprecation(method)
-
15
ActiveSupport::Deprecation.warn("`ActiveRecord::Base.configurations` no longer returns a hash. Methods that act on the hash like `#{method}` are deprecated and will be removed in Rails 6.1. Use the `configs_for` method to collect and iterate over the database configurations.")
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
-
3
module ActiveRecord
-
3
class DatabaseConfigurations
-
# Expands a connection string into a hash.
-
3
class ConnectionUrlResolver # :nodoc:
-
# == Example
-
#
-
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
-
# ConnectionUrlResolver.new(url).to_hash
-
# # => {
-
# adapter: "postgresql",
-
# host: "localhost",
-
# port: 9000,
-
# database: "foo_test",
-
# username: "foo",
-
# password: "bar",
-
# pool: "5",
-
# timeout: "3000"
-
# }
-
3
def initialize(url)
-
247
raise "Database URL cannot be empty" if url.blank?
-
247
@uri = uri_parser.parse(url)
-
247
@adapter = @uri.scheme && @uri.scheme.tr("-", "_")
-
247
@adapter = "postgresql" if @adapter == "postgres"
-
-
247
if @uri.opaque
-
10
@uri.opaque, @query = @uri.opaque.split("?", 2)
-
else
-
237
@query = @uri.query
-
end
-
end
-
-
# Converts the given URL to a full connection hash.
-
3
def to_hash
-
247
config = raw_config.compact_blank
-
989
config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
-
247
config
-
end
-
-
3
private
-
3
attr_reader :uri
-
-
3
def uri_parser
-
983
@uri_parser ||= URI::Parser.new
-
end
-
-
# Converts the query parameters of the URI into a hash.
-
#
-
# "localhost?pool=5&reaping_frequency=2"
-
# # => { pool: "5", reaping_frequency: "2" }
-
#
-
# returns empty hash if no query present.
-
#
-
# "localhost"
-
# # => {}
-
3
def query_hash
-
277
Hash[(@query || "").split("&").map { |pair| pair.split("=", 2) }].symbolize_keys
-
end
-
-
3
def raw_config
-
247
if uri.opaque
-
10
query_hash.merge(
-
adapter: @adapter,
-
database: uri.opaque
-
)
-
else
-
237
query_hash.merge(
-
adapter: @adapter,
-
username: uri.user,
-
password: uri.password,
-
port: uri.port,
-
database: database_from_path,
-
host: uri.hostname
-
)
-
end
-
end
-
-
# Returns name of the database.
-
3
def database_from_path
-
237
if @adapter == "sqlite3"
-
# 'sqlite3:/foo' is absolute, because that makes sense. The
-
# corresponding relative version, 'sqlite3:foo', is handled
-
# elsewhere, as an "opaque".
-
-
7
uri.path
-
else
-
# Only SQLite uses a filename as the "database" name; for
-
# anything else, a leading slash would be silly.
-
-
230
uri.path.delete_prefix("/")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class DatabaseConfigurations
-
# ActiveRecord::Base.configurations will return either a HashConfig or
-
# UrlConfig respectively. It will never return a DatabaseConfig object,
-
# as this is the parent class for the types of database configuration objects.
-
3
class DatabaseConfig # :nodoc:
-
3
attr_reader :env_name, :name
-
-
3
attr_accessor :owner_name
-
-
3
def initialize(env_name, name)
-
1028
@env_name = env_name
-
1028
@name = name
-
end
-
-
3
def spec_name
-
3
@name
-
end
-
3
deprecate spec_name: "please use name instead"
-
-
3
def config
-
raise NotImplementedError
-
end
-
-
3
def adapter_method
-
1625
"#{adapter}_connection"
-
end
-
-
3
def host
-
raise NotImplementedError
-
end
-
-
3
def database
-
raise NotImplementedError
-
end
-
-
3
def _database=(database)
-
raise NotImplementedError
-
end
-
-
3
def adapter
-
raise NotImplementedError
-
end
-
-
3
def pool
-
raise NotImplementedError
-
end
-
-
3
def checkout_timeout
-
raise NotImplementedError
-
end
-
-
3
def reaping_frequency
-
raise NotImplementedError
-
end
-
-
3
def idle_timeout
-
raise NotImplementedError
-
end
-
-
3
def replica?
-
raise NotImplementedError
-
end
-
-
3
def migrations_paths
-
raise NotImplementedError
-
end
-
-
3
def for_current_env?
-
1440
env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
-
end
-
-
3
def schema_cache_path
-
raise NotImplementedError
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class DatabaseConfigurations
-
# A HashConfig object is created for each database configuration entry that
-
# is created from a hash.
-
#
-
# A hash config:
-
#
-
# { "development" => { "database" => "db_name" } }
-
#
-
# Becomes:
-
#
-
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
-
# @env_name="development", @name="primary", @config={database: "db_name"}>
-
#
-
# ==== Options
-
#
-
# * <tt>:env_name</tt> - The Rails environment, i.e. "development".
-
# * <tt>:name</tt> - The db config name. In a standard two-tier
-
# database configuration this will default to "primary". In a multiple
-
# database three-tier database configuration this corresponds to the name
-
# used in the second tier, for example "primary_readonly".
-
# * <tt>:config</tt> - The config hash. This is the hash that contains the
-
# database adapter, name, and other important information for database
-
# connections.
-
3
class HashConfig < DatabaseConfig
-
3
attr_reader :configuration_hash
-
3
def initialize(env_name, name, configuration_hash)
-
1028
super(env_name, name)
-
1028
@configuration_hash = configuration_hash.symbolize_keys.freeze
-
end
-
-
3
def config
-
3
ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 6.2.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys")
-
3
configuration_hash.stringify_keys
-
end
-
-
# Determines whether a database configuration is for a replica / readonly
-
# connection. If the +replica+ key is present in the config, +replica?+ will
-
# return +true+.
-
3
def replica?
-
768
configuration_hash[:replica]
-
end
-
-
# The migrations paths for a database configuration. If the
-
# +migrations_paths+ key is present in the config, +migrations_paths+
-
# will return its value.
-
3
def migrations_paths
-
configuration_hash[:migrations_paths]
-
end
-
-
3
def host
-
44
configuration_hash[:host]
-
end
-
-
3
def database
-
409
configuration_hash[:database]
-
end
-
-
3
def _database=(database) # :nodoc:
-
8
@configuration_hash = configuration_hash.merge(database: database).freeze
-
end
-
-
3
def pool
-
984
(configuration_hash[:pool] || 5).to_i
-
end
-
-
3
def checkout_timeout
-
984
(configuration_hash[:checkout_timeout] || 5).to_f
-
end
-
-
# +reaping_frequency+ is configurable mostly for historical reasons, but it could
-
# also be useful if someone wants a very low +idle_timeout+.
-
3
def reaping_frequency
-
984
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
-
end
-
-
3
def idle_timeout
-
987
timeout = configuration_hash.fetch(:idle_timeout, 300).to_f
-
987
timeout if timeout > 0
-
end
-
-
3
def adapter
-
3287
configuration_hash[:adapter]
-
end
-
-
# The path to the schema cache dump file for a database.
-
# If omitted, the filename will be read from ENV or a
-
# default will be derived.
-
3
def schema_cache_path
-
configuration_hash[:schema_cache_path]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class DatabaseConfigurations
-
# A UrlConfig object is created for each database configuration
-
# entry that is created from a URL. This can either be a URL string
-
# or a hash with a URL in place of the config hash.
-
#
-
# A URL config:
-
#
-
# postgres://localhost/foo
-
#
-
# Becomes:
-
#
-
# #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fdc3238f340
-
# @env_name="default_env", @name="primary",
-
# @config={adapter: "postgresql", database: "foo", host: "localhost"},
-
# @url="postgres://localhost/foo">
-
#
-
# ==== Options
-
#
-
# * <tt>:env_name</tt> - The Rails environment, ie "development".
-
# * <tt>:name</tt> - The db config name. In a standard two-tier
-
# database configuration this will default to "primary". In a multiple
-
# database three-tier database configuration this corresponds to the name
-
# used in the second tier, for example "primary_readonly".
-
# * <tt>:url</tt> - The database URL.
-
# * <tt>:config</tt> - The config hash. This is the hash that contains the
-
# database adapter, name, and other important information for database
-
# connections.
-
3
class UrlConfig < HashConfig
-
3
attr_reader :url
-
-
3
def initialize(env_name, name, url, configuration_hash = {})
-
253
super(env_name, name, configuration_hash)
-
-
253
@url = url
-
253
@configuration_hash = @configuration_hash.merge(build_url_hash).freeze
-
end
-
-
3
private
-
# Return a Hash that can be merged into the main config that represents
-
# the passed in url
-
3
def build_url_hash
-
253
if url.nil? || url.start_with?("jdbc:")
-
6
{ url: url }
-
else
-
247
ConnectionUrlResolver.new(url).to_hash
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/inquiry"
-
-
3
module ActiveRecord
-
# == Delegated types
-
#
-
# Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
-
# purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
-
# where all attributes from all levels of the hierarchy are represented in a single table. Both have their
-
# places, but neither are without their drawbacks.
-
#
-
# The problem with purely abstract classes is that all concrete subclasses must persist all the shared
-
# attributes themselves in their own tables (also known as class-table inheritance). This makes it hard to
-
# do queries across the hierarchy. For example, imagine you have the following hierarchy:
-
#
-
# Entry < ApplicationRecord
-
# Message < Entry
-
# Comment < Entry
-
#
-
# How do you show a feed that has both +Message+ and +Comment+ records, which can be easily paginated?
-
# Well, you can't! Messages are backed by a messages table and comments by a comments table. You can't
-
# pull from both tables at once and use a consistent OFFSET/LIMIT scheme.
-
#
-
# You can get around the pagination problem by using single-table inheritance, but now you're forced into
-
# a single mega table with all the attributes from all subclasses. No matter how divergent. If a Message
-
# has a subject, but the comment does not, well, now the comment does anyway! So STI works best when there's
-
# little divergence between the subclasses and their attributes.
-
#
-
# But there's a third way: Delegated types. With this approach, the "superclass" is a concrete class
-
# that is represented by its own table, where all the superclass attributes that are shared amongst all the
-
# "subclasses" are stored. And then each of the subclasses have their own individual tables for additional
-
# attributes that are particular to their implementation. This is similar to what's called multi-table
-
# inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the
-
# hierarchy and share responsibilities.
-
#
-
# Let's look at that entry/message/comment example using delegated types:
-
#
-
# # Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
-
# class Entry < ApplicationRecord
-
# belongs_to :account
-
# belongs_to :creator
-
# delegated_type :entryable, types: %w[ Message Comment ]
-
# end
-
#
-
# module Entryable
-
# extend ActiveSupport::Concern
-
#
-
# included do
-
# has_one :entry, as: :entryable, touch: true
-
# end
-
# end
-
#
-
# # Schema: messages[ id, subject ]
-
# class Message < ApplicationRecord
-
# include Entryable
-
# has_rich_text :content
-
# end
-
#
-
# # Schema: comments[ id, content ]
-
# class Comment < ApplicationRecord
-
# include Entryable
-
# end
-
#
-
# As you can see, neither +Message+ nor +Comment+ are meant to stand alone. Crucial metadata for both classes
-
# resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
-
# in particular. You can now easily do things like:
-
#
-
# Account.entries.order(created_at: :desc).limit(50)
-
#
-
# Which is exactly what you want when displaying both comments and messages together. The entry itself can
-
# be rendered as its delegated type easily, like so:
-
#
-
# # entries/_entry.html.erb
-
# <%= render "entries/entryables/#{entry.entryable_name}", entry: entry %>
-
#
-
# # entries/entryables/_message.html.erb
-
# <div class="message">
-
# Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %>
-
# </div>
-
#
-
# # entries/entryables/_comment.html.erb
-
# <div class="comment">
-
# <%= entry.creator.name %> said: <%= entry.comment.content %>
-
# </div>
-
#
-
# == Sharing behavior with concerns and controllers
-
#
-
# The entry "superclass" also serves as a perfect place to put all that shared logic that applies to both
-
# messages and comments, and which acts primarily on the shared attributes. Imagine:
-
#
-
# class Entry < ApplicationRecord
-
# include Eventable, Forwardable, Redeliverable
-
# end
-
#
-
# Which allows you to have controllers for things like +ForwardsController+ and +RedeliverableController+
-
# that both act on entries, and thus provide the shared functionality to both messages and comments.
-
#
-
# == Creating new records
-
#
-
# You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
-
# like so:
-
#
-
# Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
-
#
-
# If you need more complicated composition, or you need to perform dependent validation, you should build a factory
-
# method or class to take care of the complicated needs. This could be as simple as:
-
#
-
# class Entry < ApplicationRecord
-
# def self.create_with_comment(content, creator: Current.user)
-
# create! entryable: Comment.new(content: content), creator: creator
-
# end
-
# end
-
#
-
# == Adding further delegation
-
#
-
# The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
-
# an anti-pattern most of the time. The reason you're building this hierarchy is to take advantage of polymorphism.
-
# So here's a simple example of that:
-
#
-
# class Entry < ApplicationRecord
-
# delegated_type :entryable, types: %w[ Message Comment ]
-
# delegate :title, to: :entryable
-
# end
-
#
-
# class Message < ApplicationRecord
-
# def title
-
# subject
-
# end
-
# end
-
#
-
# class Comment < ApplicationRecord
-
# def title
-
# content.truncate(20)
-
# end
-
# end
-
#
-
# Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
-
3
module DelegatedType
-
# Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
-
# That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
-
# type convenience methods:
-
#
-
# class Entry < ApplicationRecord
-
# delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
-
# end
-
#
-
# Entry#entryable_class # => +Message+ or +Comment+
-
# Entry#entryable_name # => "message" or "comment"
-
# Entry.messages # => Entry.where(entryable_type: "Message")
-
# Entry#message? # => true when entryable_type == "Message"
-
# Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
-
# Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
-
# Entry.comments # => Entry.where(entryable_type: "Comment")
-
# Entry#comment? # => true when entryable_type == "Comment"
-
# Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
-
# Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
-
#
-
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
-
#
-
# You can also declare namespaced types:
-
#
-
# class Entry < ApplicationRecord
-
# delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy
-
# end
-
#
-
# Entry.access_notice_messages
-
# entry.access_notice_message
-
# entry.access_notice_message?
-
3
def delegated_type(role, types:, **options)
-
3
belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
-
3
define_delegated_type_methods role, types: types
-
end
-
-
3
private
-
3
def define_delegated_type_methods(role, types:)
-
3
role_type = "#{role}_type"
-
3
role_id = "#{role}_id"
-
-
3
define_method "#{role}_class" do
-
18
public_send("#{role}_type").constantize
-
end
-
-
3
define_method "#{role}_name" do
-
12
public_send("#{role}_class").model_name.singular.inquiry
-
end
-
-
3
types.each do |type|
-
6
scope_name = type.tableize.gsub("/", "_")
-
6
singular = scope_name.singularize
-
6
query = "#{singular}?"
-
-
12
scope scope_name, -> { where(role_type => type) }
-
-
6
define_method query do
-
42
public_send(role_type) == type
-
end
-
-
6
define_method singular do
-
12
public_send(role) if public_send(query)
-
end
-
-
6
define_method "#{singular}_id" do
-
12
public_send(role_id) if public_send(query)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module DynamicMatchers #:nodoc:
-
3
private
-
3
def respond_to_missing?(name, _)
-
398295
if self == Base
-
1332
super
-
else
-
396963
match = Method.match(self, name)
-
396963
match && match.valid? || super
-
end
-
end
-
-
3
def method_missing(name, *arguments, &block)
-
148
match = Method.match(self, name)
-
-
148
if match && match.valid?
-
130
match.define
-
130
send(name, *arguments, &block)
-
else
-
18
super
-
end
-
end
-
-
3
class Method
-
3
@matchers = []
-
-
3
class << self
-
3
attr_reader :matchers
-
-
3
def match(model, name)
-
1191161
klass = matchers.find { |k| k.pattern.match?(name) }
-
397111
klass.new(model, name) if klass
-
end
-
-
3
def pattern
-
794241
@pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
-
end
-
-
3
def prefix
-
raise NotImplementedError
-
end
-
-
3
def suffix
-
3
""
-
end
-
end
-
-
3
attr_reader :model, :name, :attribute_names
-
-
3
def initialize(model, method_name)
-
191
@model = model
-
191
@name = method_name.to_s
-
191
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
-
414
@attribute_names.map! { |name| @model.attribute_aliases[name] || name }
-
end
-
-
3
def valid?
-
414
attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
-
end
-
-
3
def define
-
130
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def self.#{name}(#{signature})
-
#{body}
-
end
-
CODE
-
end
-
-
3
private
-
3
def body
-
130
"#{finder}(#{attributes_hash})"
-
end
-
-
# The parameters in the signature may have reserved Ruby words, in order
-
# to prevent errors, we start each param name with `_`.
-
3
def signature
-
281
attribute_names.map { |name| "_#{name}" }.join(", ")
-
end
-
-
# Given that the parameters starts with `_`, the finder needs to use the
-
# same parameter name.
-
3
def attributes_hash
-
281
"{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}"
-
end
-
-
3
def finder
-
raise NotImplementedError
-
end
-
end
-
-
3
class FindBy < Method
-
3
Method.matchers << self
-
-
3
def self.prefix
-
3
"find_by"
-
end
-
-
3
def finder
-
120
"find_by"
-
end
-
end
-
-
3
class FindByBang < Method
-
3
Method.matchers << self
-
-
3
def self.prefix
-
3
"find_by"
-
end
-
-
3
def self.suffix
-
3
"!"
-
end
-
-
3
def finder
-
10
"find_by!"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/object/deep_dup"
-
-
3
module ActiveRecord
-
# Declare an enum attribute where the values map to integers in the database,
-
# but can be queried by name. Example:
-
#
-
# class Conversation < ActiveRecord::Base
-
# enum status: [ :active, :archived ]
-
# end
-
#
-
# # conversation.update! status: 0
-
# conversation.active!
-
# conversation.active? # => true
-
# conversation.status # => "active"
-
#
-
# # conversation.update! status: 1
-
# conversation.archived!
-
# conversation.archived? # => true
-
# conversation.status # => "archived"
-
#
-
# # conversation.status = 1
-
# conversation.status = "archived"
-
#
-
# conversation.status = nil
-
# conversation.status.nil? # => true
-
# conversation.status # => nil
-
#
-
# Scopes based on the allowed values of the enum field will be provided
-
# as well. With the above example:
-
#
-
# Conversation.active
-
# Conversation.not_active
-
# Conversation.archived
-
# Conversation.not_archived
-
#
-
# Of course, you can also query them directly if the scopes don't fit your
-
# needs:
-
#
-
# Conversation.where(status: [:active, :archived])
-
# Conversation.where.not(status: :active)
-
#
-
# Defining scopes can be disabled by setting +:_scopes+ to +false+.
-
#
-
# class Conversation < ActiveRecord::Base
-
# enum status: [ :active, :archived ], _scopes: false
-
# end
-
#
-
# You can set the default enum value by setting +:_default+, like:
-
#
-
# class Conversation < ActiveRecord::Base
-
# enum status: [ :active, :archived ], _default: "active"
-
# end
-
#
-
# conversation = Conversation.new
-
# conversation.status # => "active"
-
#
-
# Finally, it's also possible to explicitly map the relation between attribute and
-
# database integer with a hash:
-
#
-
# class Conversation < ActiveRecord::Base
-
# enum status: { active: 0, archived: 1 }
-
# end
-
#
-
# Note that when an array is used, the implicit mapping from the values to database
-
# integers is derived from the order the values appear in the array. In the example,
-
# <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
-
# is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the
-
# database.
-
#
-
# Therefore, once a value is added to the enum array, its position in the array must
-
# be maintained, and new values should only be added to the end of the array. To
-
# remove unused values, the explicit hash syntax should be used.
-
#
-
# In rare circumstances you might need to access the mapping directly.
-
# The mappings are exposed through a class method with the pluralized attribute
-
# name, which return the mapping in a +HashWithIndifferentAccess+:
-
#
-
# Conversation.statuses[:active] # => 0
-
# Conversation.statuses["archived"] # => 1
-
#
-
# Use that class method when you need to know the ordinal value of an enum.
-
# For example, you can use that when manually building SQL strings:
-
#
-
# Conversation.where("status <> ?", Conversation.statuses[:archived])
-
#
-
# You can use the +:_prefix+ or +:_suffix+ options when you need to define
-
# multiple enums with same values. If the passed value is +true+, the methods
-
# are prefixed/suffixed with the name of the enum. It is also possible to
-
# supply a custom value:
-
#
-
# class Conversation < ActiveRecord::Base
-
# enum status: [:active, :archived], _suffix: true
-
# enum comments_status: [:active, :inactive], _prefix: :comments
-
# end
-
#
-
# With the above example, the bang and predicate methods along with the
-
# associated scopes are now prefixed and/or suffixed accordingly:
-
#
-
# conversation.active_status!
-
# conversation.archived_status? # => false
-
#
-
# conversation.comments_inactive!
-
# conversation.comments_active? # => false
-
-
3
module Enum
-
3
def self.extended(base) # :nodoc:
-
3
base.class_attribute(:defined_enums, instance_writer: false, default: {})
-
end
-
-
3
def inherited(base) # :nodoc:
-
2940
base.defined_enums = defined_enums.deep_dup
-
2940
super
-
end
-
-
3
class EnumType < Type::Value # :nodoc:
-
3
delegate :type, to: :subtype
-
-
3
def initialize(name, mapping, subtype)
-
291
@name = name
-
291
@mapping = mapping
-
291
@subtype = subtype
-
end
-
-
3
def cast(value)
-
381
if mapping.has_key?(value)
-
156
value.to_s
-
225
elsif mapping.has_value?(value)
-
207
mapping.key(value)
-
18
elsif value.blank?
-
nil
-
else
-
assert_valid_value(value)
-
end
-
end
-
-
3
def deserialize(value)
-
2684
mapping.key(subtype.deserialize(value))
-
end
-
-
3
def serializable?(value)
-
309
(value.blank? || mapping.has_key?(value) || mapping.has_value?(value)) && super
-
end
-
-
3
def serialize(value)
-
2340
mapping.fetch(value, value)
-
end
-
-
3
def assert_valid_value(value)
-
267
unless serializable?(value)
-
3
raise ArgumentError, "'#{value}' is not a valid #{name}"
-
end
-
end
-
-
3
private
-
3
attr_reader :name, :mapping, :subtype
-
end
-
-
3
def enum(definitions)
-
141
klass = self
-
-
141
enum_prefix = definitions.delete(:_prefix)
-
141
enum_suffix = definitions.delete(:_suffix)
-
141
enum_scopes = definitions.delete(:_scopes)
-
-
141
default = {}
-
141
default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
-
-
141
definitions.each do |name, values|
-
144
assert_valid_enum_definition_values(values)
-
# statuses = { }
-
135
enum_values = ActiveSupport::HashWithIndifferentAccess.new
-
135
name = name.to_s
-
-
# def self.statuses() statuses end
-
135
detect_enum_conflict!(name, name.pluralize, true)
-
159
singleton_class.define_method(name.pluralize) { enum_values }
-
132
defined_enums[name] = enum_values
-
-
132
detect_enum_conflict!(name, name)
-
126
detect_enum_conflict!(name, "#{name}=")
-
-
126
attr = attribute_alias?(name) ? attribute_alias(name) : name
-
-
126
decorate_attribute_type(attr, **default) do |subtype|
-
291
EnumType.new(attr, enum_values, subtype)
-
end
-
-
126
_enum_methods_module.module_eval do
-
126
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
-
126
pairs.each do |label, value|
-
276
if enum_prefix == true
-
12
prefix = "#{name}_"
-
264
elsif enum_prefix
-
18
prefix = "#{enum_prefix}_"
-
end
-
276
if enum_suffix == true
-
9
suffix = "_#{name}"
-
267
elsif enum_suffix
-
9
suffix = "_#{enum_suffix}"
-
end
-
-
276
value_method_name = "#{prefix}#{label}#{suffix}"
-
276
enum_values[label] = value
-
276
label = label.to_s
-
-
# def active?() status == "active" end
-
276
klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
-
465
define_method("#{value_method_name}?") { self[attr] == label }
-
-
# def active!() update!(status: 0) end
-
270
klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
-
288
define_method("#{value_method_name}!") { update!(attr => value) }
-
-
# scope :active, -> { where(status: 0) }
-
# scope :not_active, -> { where.not(status: 0) }
-
267
if enum_scopes != false
-
261
klass.send(:detect_negative_condition!, value_method_name)
-
-
261
klass.send(:detect_enum_conflict!, name, value_method_name, true)
-
309
klass.scope value_method_name, -> { where(attr => value) }
-
-
231
klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
-
237
klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
-
end
-
end
-
end
-
87
enum_values.freeze
-
end
-
end
-
-
3
private
-
3
def _enum_methods_module
-
918
@_enum_methods_module ||= begin
-
66
mod = Module.new
-
66
include mod
-
66
mod
-
end
-
end
-
-
3
def assert_valid_enum_definition_values(values)
-
417
unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
-
3
error_message = <<~MSG
-
Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
-
MSG
-
3
raise ArgumentError, error_message
-
end
-
-
141
if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
-
6
raise ArgumentError, "Enum label name must not be blank."
-
end
-
end
-
-
3
ENUM_CONFLICT_MESSAGE = \
-
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
-
"this will generate a %{type} method \"%{method}\", which is already defined " \
-
"by %{source}."
-
3
private_constant :ENUM_CONFLICT_MESSAGE
-
-
3
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
-
1431
if klass_method && dangerous_class_method?(method_name)
-
24
raise_conflict_error(enum_name, method_name, type: "class")
-
1407
elsif klass_method && method_defined_within?(method_name, Relation)
-
9
raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
-
1398
elsif !klass_method && dangerous_attribute_method?(method_name)
-
12
raise_conflict_error(enum_name, method_name)
-
1386
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
-
3
raise_conflict_error(enum_name, method_name, source: "another enum")
-
end
-
end
-
-
3
def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record")
-
48
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
-
enum: enum_name,
-
klass: name,
-
type: type,
-
method: method_name,
-
source: source
-
}
-
end
-
-
3
def detect_negative_condition!(method_name)
-
261
if method_name.start_with?("not_") && logger
-
3
logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
-
" This will cause a conflict with auto generated negative scopes."
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record Errors
-
#
-
# Generic Active Record exception class.
-
3
class ActiveRecordError < StandardError
-
end
-
-
# Raised when the single-table inheritance mechanism fails to locate the subclass
-
# (for example due to improper usage of column that
-
# {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
-
# points to).
-
3
class SubclassNotFound < ActiveRecordError
-
end
-
-
# Raised when an object assigned to an association has an incorrect type.
-
#
-
# class Ticket < ActiveRecord::Base
-
# has_many :patches
-
# end
-
#
-
# class Patch < ActiveRecord::Base
-
# belongs_to :ticket
-
# end
-
#
-
# # Comments are not patches, this assignment raises AssociationTypeMismatch.
-
# @ticket.patches << Comment.new(content: "Please attach tests to your patch.")
-
3
class AssociationTypeMismatch < ActiveRecordError
-
end
-
-
# Raised when unserialized object's type mismatches one specified for serializable field.
-
3
class SerializationTypeMismatch < ActiveRecordError
-
end
-
-
# Raised when adapter not specified on connection (or configuration file
-
# +config/database.yml+ misses adapter field).
-
3
class AdapterNotSpecified < ActiveRecordError
-
end
-
-
# Raised when a model makes a query but it has not specified an associated table.
-
3
class TableNotSpecified < ActiveRecordError
-
end
-
-
# Raised when Active Record cannot find database adapter specified in
-
# +config/database.yml+ or programmatically.
-
3
class AdapterNotFound < ActiveRecordError
-
end
-
-
# Raised when connection to the database could not been established (for example when
-
# {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection]
-
# is given a +nil+ object).
-
3
class ConnectionNotEstablished < ActiveRecordError
-
end
-
-
# Raised when a write to the database is attempted on a read only connection.
-
3
class ReadOnlyError < ActiveRecordError
-
end
-
-
# Raised when Active Record cannot find a record by given id or set of ids.
-
3
class RecordNotFound < ActiveRecordError
-
3
attr_reader :model, :primary_key, :id
-
-
3
def initialize(message = nil, model = nil, primary_key = nil, id = nil)
-
332
@primary_key = primary_key
-
332
@model = model
-
332
@id = id
-
-
332
super(message)
-
end
-
end
-
-
# Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
-
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
-
# methods when a record is invalid and cannot be saved.
-
3
class RecordNotSaved < ActiveRecordError
-
3
attr_reader :record
-
-
3
def initialize(message = nil, record = nil)
-
52
@record = record
-
52
super(message)
-
end
-
end
-
-
# Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!]
-
# when a call to {#destroy}[rdoc-ref:Persistence#destroy!]
-
# would return false.
-
#
-
# begin
-
# complex_operation_that_internally_calls_destroy!
-
# rescue ActiveRecord::RecordNotDestroyed => invalid
-
# puts invalid.record.errors
-
# end
-
#
-
3
class RecordNotDestroyed < ActiveRecordError
-
3
attr_reader :record
-
-
3
def initialize(message = nil, record = nil)
-
18
@record = record
-
18
super(message)
-
end
-
end
-
-
# Superclass for all database execution errors.
-
#
-
# Wraps the underlying database error as +cause+.
-
3
class StatementInvalid < ActiveRecordError
-
3
def initialize(message = nil, sql: nil, binds: nil)
-
2367
super(message || $!&.message)
-
2367
@sql = sql
-
2367
@binds = binds
-
end
-
-
3
attr_reader :sql, :binds
-
end
-
-
# Defunct wrapper class kept for compatibility.
-
# StatementInvalid wraps the original exception now.
-
3
class WrappedDatabaseException < StatementInvalid
-
end
-
-
# Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint.
-
3
class RecordNotUnique < WrappedDatabaseException
-
end
-
-
# Raised when a record cannot be inserted or updated because it references a non-existent record,
-
# or when a record cannot be deleted because a parent record references it.
-
3
class InvalidForeignKey < WrappedDatabaseException
-
end
-
-
# Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
-
3
class MismatchedForeignKey < StatementInvalid
-
3
def initialize(
-
message: nil,
-
sql: nil,
-
binds: nil,
-
table: nil,
-
foreign_key: nil,
-
target_table: nil,
-
primary_key: nil,
-
primary_key_column: nil
-
)
-
3
if table
-
type = primary_key_column.bigint? ? :bigint : primary_key_column.type
-
msg = <<~EOM.squish
-
Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
-
which has type `#{primary_key_column.sql_type}`.
-
To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
-
(For example `t.#{type} :#{foreign_key}`).
-
EOM
-
else
-
3
msg = <<~EOM.squish
-
There is a mismatch between the foreign key and primary key column types.
-
Verify that the foreign key column type and the primary key of the associated table match types.
-
EOM
-
end
-
3
if message
-
msg << "\nOriginal message: #{message}"
-
end
-
3
super(msg, sql: sql, binds: binds)
-
end
-
end
-
-
# Raised when a record cannot be inserted or updated because it would violate a not null constraint.
-
3
class NotNullViolation < StatementInvalid
-
end
-
-
# Raised when a record cannot be inserted or updated because a value too long for a column type.
-
3
class ValueTooLong < StatementInvalid
-
end
-
-
# Raised when values that executed are out of range.
-
3
class RangeError < StatementInvalid
-
end
-
-
# Raised when the number of placeholders in an SQL fragment passed to
-
# {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]
-
# does not match the number of values supplied.
-
#
-
# For example, when there are two placeholders with only one value supplied:
-
#
-
# Location.where("lat = ? AND lng = ?", 53.7362)
-
3
class PreparedStatementInvalid < ActiveRecordError
-
end
-
-
# Raised when a given database does not exist.
-
3
class NoDatabaseError < StatementInvalid
-
end
-
-
# Raised when creating a database if it exists.
-
3
class DatabaseAlreadyExists < StatementInvalid
-
end
-
-
# Raised when PostgreSQL returns 'cached plan must not change result type' and
-
# we cannot retry gracefully (e.g. inside a transaction)
-
3
class PreparedStatementCacheExpired < StatementInvalid
-
end
-
-
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
-
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
-
# the page before the other.
-
#
-
# Read more about optimistic locking in ActiveRecord::Locking module
-
# documentation.
-
3
class StaleObjectError < ActiveRecordError
-
3
attr_reader :record, :attempted_action
-
-
3
def initialize(record = nil, attempted_action = nil)
-
57
if record && attempted_action
-
54
@record = record
-
54
@attempted_action = attempted_action
-
54
super("Attempted to #{attempted_action} a stale object: #{record.class.name}.")
-
else
-
3
super("Stale object error.")
-
end
-
end
-
end
-
-
# Raised when association is being configured improperly or user tries to use
-
# offset and limit together with
-
# {ActiveRecord::Base.has_many}[rdoc-ref:Associations::ClassMethods#has_many] or
-
# {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many]
-
# associations.
-
3
class ConfigurationError < ActiveRecordError
-
end
-
-
# Raised on attempt to update record that is instantiated as read only.
-
3
class ReadOnlyRecord < ActiveRecordError
-
end
-
-
# Raised on attempt to lazily load records that are marked as strict loading.
-
3
class StrictLoadingViolationError < ActiveRecordError
-
end
-
-
# {ActiveRecord::Base.transaction}[rdoc-ref:Transactions::ClassMethods#transaction]
-
# uses this exception to distinguish a deliberate rollback from other exceptional situations.
-
# Normally, raising an exception will cause the
-
# {.transaction}[rdoc-ref:Transactions::ClassMethods#transaction] method to rollback
-
# the database transaction *and* pass on the exception. But if you raise an
-
# ActiveRecord::Rollback exception, then the database transaction will be rolled back,
-
# without passing on the exception.
-
#
-
# For example, you could do this in your controller to rollback a transaction:
-
#
-
# class BooksController < ActionController::Base
-
# def create
-
# Book.transaction do
-
# book = Book.new(params[:book])
-
# book.save!
-
# if today_is_friday?
-
# # The system must fail on Friday so that our support department
-
# # won't be out of job. We silently rollback this transaction
-
# # without telling the user.
-
# raise ActiveRecord::Rollback, "Call tech support!"
-
# end
-
# end
-
# # ActiveRecord::Rollback is the only exception that won't be passed on
-
# # by ActiveRecord::Base.transaction, so this line will still be reached
-
# # even on Friday.
-
# redirect_to root_url
-
# end
-
# end
-
3
class Rollback < ActiveRecordError
-
end
-
-
# Raised when attribute has a name reserved by Active Record (when attribute
-
# has name of one of Active Record instance methods).
-
3
class DangerousAttributeError < ActiveRecordError
-
end
-
-
# Raised when unknown attributes are supplied via mass assignment.
-
3
UnknownAttributeError = ActiveModel::UnknownAttributeError
-
-
# Raised when an error occurred while doing a mass assignment to an attribute through the
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
-
# The exception has an +attribute+ property that is the name of the offending attribute.
-
3
class AttributeAssignmentError < ActiveRecordError
-
3
attr_reader :exception, :attribute
-
-
3
def initialize(message = nil, exception = nil, attribute = nil)
-
27
super(message)
-
27
@exception = exception
-
27
@attribute = attribute
-
end
-
end
-
-
# Raised when there are multiple errors while doing a mass assignment through the
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
-
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
-
# objects, each corresponding to the error while assigning to an attribute.
-
3
class MultiparameterAssignmentErrors < ActiveRecordError
-
3
attr_reader :errors
-
-
3
def initialize(errors = nil)
-
27
@errors = errors
-
end
-
end
-
-
# Raised when a primary key is needed, but not specified in the schema or model.
-
3
class UnknownPrimaryKey < ActiveRecordError
-
3
attr_reader :model
-
-
3
def initialize(model = nil, description = nil)
-
21
if model
-
18
message = "Unknown primary key for table #{model.table_name} in model #{model}."
-
18
message += "\n#{description}" if description
-
18
@model = model
-
18
super(message)
-
else
-
3
super("Unknown primary key.")
-
end
-
end
-
end
-
-
# Raised when a relation cannot be mutated because it's already loaded.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# relation = Task.all
-
# relation.loaded? # => true
-
#
-
# # Methods which try to mutate a loaded relation fail.
-
# relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
-
# relation.limit!(5) # => ActiveRecord::ImmutableRelation
-
3
class ImmutableRelation < ActiveRecordError
-
end
-
-
# TransactionIsolationError will be raised under the following conditions:
-
#
-
# * The adapter does not support setting the isolation level
-
# * You are joining an existing open transaction
-
# * You are creating a nested (savepoint) transaction
-
#
-
# The mysql2 and postgresql adapters support setting the transaction isolation level.
-
3
class TransactionIsolationError < ActiveRecordError
-
end
-
-
# TransactionRollbackError will be raised when a transaction is rolled
-
# back by the database due to a serialization failure or a deadlock.
-
#
-
# See the following:
-
#
-
# * https://www.postgresql.org/docs/current/static/transaction-iso.html
-
# * https://dev.mysql.com/doc/refman/en/server-error-reference.html#error_er_lock_deadlock
-
3
class TransactionRollbackError < StatementInvalid
-
end
-
-
# SerializationFailure will be raised when a transaction is rolled
-
# back by the database due to a serialization failure.
-
3
class SerializationFailure < TransactionRollbackError
-
end
-
-
# Deadlocked will be raised when a transaction is rolled
-
# back by the database when a deadlock is encountered.
-
3
class Deadlocked < TransactionRollbackError
-
end
-
-
# IrreversibleOrderError is raised when a relation's order is too complex for
-
# +reverse_order+ to automatically reverse.
-
3
class IrreversibleOrderError < ActiveRecordError
-
end
-
-
# Superclass for errors that have been aborted (either by client or server).
-
3
class QueryAborted < StatementInvalid
-
end
-
-
# LockWaitTimeout will be raised when lock wait timeout exceeded.
-
3
class LockWaitTimeout < StatementInvalid
-
end
-
-
# StatementTimeout will be raised when statement timeout exceeded.
-
3
class StatementTimeout < QueryAborted
-
end
-
-
# QueryCanceled will be raised when canceling statement due to user request.
-
3
class QueryCanceled < QueryAborted
-
end
-
-
# AdapterTimeout will be raised when database clients times out while waiting from the server.
-
3
class AdapterTimeout < QueryAborted
-
end
-
-
# UnknownAttributeReference is raised when an unknown and potentially unsafe
-
# value is passed to a query method when allow_unsafe_raw_sql is set to
-
# :disabled. For example, passing a non column name value to a relation's
-
# #order method might cause this exception.
-
#
-
# When working around this exception, caution should be taken to avoid SQL
-
# injection vulnerabilities when passing user-provided values to query
-
# methods. Known-safe values can be passed to query methods by wrapping them
-
# in Arel.sql.
-
#
-
# For example, with allow_unsafe_raw_sql set to :disabled, the following
-
# code would raise this exception:
-
#
-
# Post.order("length(title)").first
-
#
-
# The desired result can be accomplished by wrapping the known-safe string
-
# in Arel.sql:
-
#
-
# Post.order(Arel.sql("length(title)")).first
-
#
-
# Again, such a workaround should *not* be used when passing user-provided
-
# values, such as request parameters or model attributes to query methods.
-
3
class UnknownAttributeReference < ActiveRecordError
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/explain_registry"
-
-
3
module ActiveRecord
-
3
module Explain
-
# Executes the block with the collect flag enabled. Queries are collected
-
# asynchronously by the subscriber and returned.
-
3
def collecting_queries_for_explain # :nodoc:
-
15
ExplainRegistry.collect = true
-
15
yield
-
15
ExplainRegistry.queries
-
ensure
-
15
ExplainRegistry.reset
-
end
-
-
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
-
# Returns a formatted string ready to be logged.
-
3
def exec_explain(queries) # :nodoc:
-
18
str = queries.map do |sql, binds|
-
27
msg = +"EXPLAIN for: #{sql}"
-
27
unless binds.empty?
-
21
msg << " "
-
42
msg << binds.map { |attr| render_bind(attr) }.inspect
-
end
-
27
msg << "\n"
-
27
msg << connection.explain(sql, binds)
-
end.join("\n")
-
-
# Overriding inspect to be more human readable, especially in the console.
-
18
def str.inspect
-
self
-
end
-
-
18
str
-
end
-
-
3
private
-
3
def render_bind(attr)
-
21
if ActiveModel::Attribute === attr
-
21
value = if attr.type.binary? && attr.value
-
"<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
-
else
-
21
connection.type_cast(attr.value_for_database)
-
end
-
else
-
value = connection.type_cast(attr)
-
attr = nil
-
end
-
-
21
[attr&.name, value]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/per_thread_registry"
-
-
3
module ActiveRecord
-
# This is a thread locals registry for EXPLAIN. For example
-
#
-
# ActiveRecord::ExplainRegistry.queries
-
#
-
# returns the collected queries local to the current thread.
-
#
-
# See the documentation of ActiveSupport::PerThreadRegistry
-
# for further details.
-
3
class ExplainRegistry # :nodoc:
-
3
extend ActiveSupport::PerThreadRegistry
-
-
3
attr_accessor :queries, :collect
-
-
3
def initialize
-
116
reset
-
end
-
-
3
def collect?
-
428045
@collect
-
end
-
-
3
def reset
-
173
@collect = false
-
173
@queries = []
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/notifications"
-
3
require "active_record/explain_registry"
-
-
3
module ActiveRecord
-
3
class ExplainSubscriber # :nodoc:
-
3
def start(name, id, payload)
-
# unused
-
end
-
-
3
def finish(name, id, payload)
-
428045
if ExplainRegistry.collect? && !ignore_payload?(payload)
-
24
ExplainRegistry.queries << payload.values_at(:sql, :binds)
-
end
-
end
-
-
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
-
# our own EXPLAINs no matter how loopingly beautiful that would be.
-
#
-
# On the other hand, we want to monitor the performance of our real database
-
# queries, not the performance of the access to the query cache.
-
3
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
-
3
EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
-
3
def ignore_payload?(payload)
-
39
payload[:exception] ||
-
payload[:cached] ||
-
IGNORED_PAYLOADS.include?(payload[:name]) ||
-
!payload[:sql].match?(EXPLAINED_SQLS)
-
end
-
-
3
ActiveSupport::Notifications.subscribe("sql.active_record", new)
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/configuration_file"
-
-
3
module ActiveRecord
-
3
class FixtureSet
-
3
class File # :nodoc:
-
3
include Enumerable
-
-
##
-
# Open a fixture file named +file+. When called with a block, the block
-
# is called with the filehandle and the filehandle is automatically closed
-
# when the block finishes.
-
3
def self.open(file)
-
7094
x = new file
-
7094
block_given? ? yield(x) : x
-
end
-
-
3
def initialize(file)
-
7094
@file = file
-
end
-
-
3
def each(&block)
-
7079
rows.each(&block)
-
end
-
-
3
def model_class
-
7073
config_row["model_class"]
-
end
-
-
3
def ignored_fixtures
-
6278
config_row["ignore"]
-
end
-
-
3
private
-
3
def rows
-
301640
@rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" }
-
end
-
-
3
def config_row
-
13351
@config_row ||= begin
-
300659
row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" }
-
7043
if row
-
138
row.last
-
else
-
6905
{ 'model_class': nil, 'ignore': nil }
-
end
-
end
-
end
-
-
3
def raw_rows
-
14134
@raw_rows ||= begin
-
7094
data = ActiveSupport::ConfigurationFile.parse(@file, context:
-
ActiveRecord::FixtureSet::RenderContext.create_subclass.new.get_binding)
-
7085
data ? validate(data).to_a : []
-
rescue RuntimeError => error
-
3
raise Fixture::FormatError, error.message
-
end
-
end
-
-
# Validate our unmarshalled data.
-
3
def validate(data)
-
7085
unless Hash === data || YAML::Omap === data
-
6
raise Fixture::FormatError, "fixture is not a hash: #{@file}"
-
end
-
-
301655
invalid = data.reject { |_, row| Hash === row }
-
7079
if invalid.any?
-
6
raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}"
-
end
-
7073
data
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class FixtureSet
-
3
class ModelMetadata # :nodoc:
-
3
def initialize(model_class)
-
5831
@model_class = model_class
-
end
-
-
3
def primary_key_name
-
307412
@primary_key_name ||= @model_class && @model_class.primary_key
-
end
-
-
3
def primary_key_type
-
2393
@primary_key_type ||= @model_class && @model_class.type_for_attribute(@model_class.primary_key).type
-
end
-
-
3
def has_primary_key_column?
-
291744
@has_primary_key_column ||= primary_key_name &&
-
5741
@model_class.columns.any? { |col| col.name == primary_key_name }
-
end
-
-
3
def timestamp_column_names
-
291570
@model_class.all_timestamp_attributes_in_model
-
end
-
-
3
def inheritance_column_name
-
303075
@inheritance_column_name ||= @model_class && @model_class.inheritance_column
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# NOTE: This class has to be defined in compact style in
-
# order for rendering context subclassing to work correctly.
-
3
class ActiveRecord::FixtureSet::RenderContext # :nodoc:
-
3
def self.create_subclass
-
7094
Class.new(ActiveRecord::FixtureSet.context_class) do
-
7094
def get_binding
-
7094
binding()
-
end
-
-
7094
def binary(path)
-
100
%(!!binary "#{Base64.strict_encode64(File.binread(path))}")
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class FixtureSet
-
3
class TableRow # :nodoc:
-
3
class ReflectionProxy # :nodoc:
-
3
def initialize(association)
-
382484
@association = association
-
end
-
-
3
def join_table
-
@association.join_table
-
end
-
-
3
def name
-
382484
@association.name
-
end
-
-
3
def primary_key_type
-
1175
@association.klass.type_for_attribute(@association.klass.primary_key).type
-
end
-
end
-
-
3
class HasManyThroughProxy < ReflectionProxy # :nodoc:
-
3
def rhs_key
-
1175
@association.foreign_key
-
end
-
-
3
def lhs_key
-
1175
@association.through_reflection.foreign_key
-
end
-
-
3
def join_table
-
1175
@association.through_reflection.table_name
-
end
-
end
-
-
3
def initialize(fixture, table_rows:, label:, now:)
-
293509
@table_rows = table_rows
-
293509
@label = label
-
293509
@now = now
-
293509
@row = fixture.to_hash
-
293509
fill_row_model_attributes
-
end
-
-
3
def to_hash
-
293509
@row
-
end
-
-
3
private
-
3
def model_metadata
-
1184595
@table_rows.model_metadata
-
end
-
-
3
def model_class
-
1157566
@table_rows.model_class
-
end
-
-
3
def fill_row_model_attributes
-
293509
return unless model_class
-
291744
fill_timestamps
-
291744
interpolate_label
-
291744
generate_primary_key
-
291744
resolve_enums
-
291744
resolve_sti_reflections
-
end
-
-
3
def reflection_class
-
293001
@reflection_class ||= if @row.include?(model_metadata.inheritance_column_name)
-
11331
@row[model_metadata.inheritance_column_name].constantize rescue model_class
-
else
-
280413
model_class
-
end
-
end
-
-
3
def fill_timestamps
-
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
-
291744
if model_class.record_timestamps
-
291570
model_metadata.timestamp_column_names.each do |c_name|
-
39812
@row[c_name] = @now unless @row.key?(c_name)
-
end
-
end
-
end
-
-
3
def interpolate_label
-
# interpolate the fixture label
-
291744
@row.each do |key, value|
-
691617
@row[key] = value.gsub("$LABEL", @label.to_s) if value.is_a?(String)
-
end
-
end
-
-
3
def generate_primary_key
-
# generate a primary key if necessary
-
291744
if model_metadata.has_primary_key_column? && !@row.include?(model_metadata.primary_key_name)
-
2393
@row[model_metadata.primary_key_name] = ActiveRecord::FixtureSet.identify(
-
@label, model_metadata.primary_key_type
-
)
-
end
-
end
-
-
3
def resolve_enums
-
291744
model_class.defined_enums.each do |name, values|
-
5106
if @row.include?(name)
-
1554
@row[name] = values.fetch(@row[name], @row[name])
-
end
-
end
-
end
-
-
3
def resolve_sti_reflections
-
# If STI is used, find the correct subclass for association reflection
-
291744
reflection_class._reflections.each_value do |association|
-
1435473
case association.macro
-
when :belongs_to
-
# Do not replace association name with association foreign key if they are named the same
-
339703
fk_name = association.join_foreign_key
-
-
339703
if association.name.to_s != fk_name && value = @row.delete(association.name.to_s)
-
1257
if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
-
# support polymorphic belongs_to as "label (Type)"
-
220
@row[association.join_foreign_type] = $1
-
end
-
-
1257
fk_type = reflection_class.type_for_attribute(fk_name).type
-
1257
@row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
-
end
-
when :has_many
-
1012199
if association.options[:through]
-
382484
add_join_records(HasManyThroughProxy.new(association))
-
end
-
end
-
end
-
end
-
-
3
def add_join_records(association)
-
# This is the case when the join table has no fixtures file
-
382484
if (targets = @row.delete(association.name.to_s))
-
1175
table_name = association.join_table
-
1175
column_type = association.primary_key_type
-
1175
lhs_key = association.lhs_key
-
1175
rhs_key = association.rhs_key
-
-
1175
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
-
1175
joins = targets.map do |target|
-
1793
{ lhs_key => @row[model_metadata.primary_key_name],
-
rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
-
end
-
1175
@table_rows.tables[table_name].concat(joins)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/fixture_set/table_row"
-
3
require "active_record/fixture_set/model_metadata"
-
-
3
module ActiveRecord
-
3
class FixtureSet
-
3
class TableRows # :nodoc:
-
3
def initialize(table_name, model_class:, fixtures:, config:)
-
6269
@model_class = model_class
-
-
# track any join tables we need to insert later
-
7102
@tables = Hash.new { |h, table| h[table] = [] }
-
-
# ensure this table is loaded before any HABTM associations
-
6269
@tables[table_name] = nil
-
-
6269
build_table_rows_from(table_name, fixtures, config)
-
end
-
-
3
attr_reader :tables, :model_class
-
-
3
def to_hash
-
13371
@tables.transform_values { |rows| rows.map(&:to_hash) }
-
end
-
-
3
def model_metadata
-
1184595
@model_metadata ||= ModelMetadata.new(model_class)
-
end
-
-
3
private
-
3
def build_table_rows_from(table_name, fixtures, config)
-
6269
now = config.default_timezone == :utc ? Time.now.utc : Time.now
-
-
6269
@tables[table_name] = fixtures.map do |label, fixture|
-
293509
TableRow.new(
-
fixture,
-
table_rows: self,
-
label: label,
-
now: now,
-
)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "erb"
-
3
require "yaml"
-
3
require "zlib"
-
3
require "set"
-
3
require "active_support/dependencies"
-
3
require "active_support/core_ext/digest/uuid"
-
3
require "active_record/fixture_set/file"
-
3
require "active_record/fixture_set/render_context"
-
3
require "active_record/fixture_set/table_rows"
-
3
require "active_record/test_fixtures"
-
-
3
module ActiveRecord
-
3
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
-
end
-
-
# \Fixtures are a way of organizing data that you want to test against; in short, sample data.
-
#
-
# They are stored in YAML files, one file per model, which are placed in the directory
-
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
-
# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
-
# The fixture file ends with the +.yml+ file extension, for example:
-
# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
-
#
-
# The format of a fixture file looks like this:
-
#
-
# rubyonrails:
-
# id: 1
-
# name: Ruby on Rails
-
# url: http://www.rubyonrails.org
-
#
-
# google:
-
# id: 2
-
# name: Google
-
# url: http://www.google.com
-
#
-
# This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
-
# is followed by an indented list of key/value pairs in the "key: value" format. Records are
-
# separated by a blank line for your viewing pleasure.
-
#
-
# Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
-
# See https://yaml.org/type/omap.html
-
# for the specification. You will need ordered fixtures when you have foreign key constraints
-
# on keys in the same table. This is commonly needed for tree structures. Example:
-
#
-
# --- !omap
-
# - parent:
-
# id: 1
-
# parent_id: NULL
-
# title: Parent
-
# - child:
-
# id: 2
-
# parent_id: 1
-
# title: Child
-
#
-
# = Using Fixtures in Test Cases
-
#
-
# Since fixtures are a testing construct, we use them in our unit and functional tests. There
-
# are two ways to use the fixtures, but first let's take a look at a sample unit test:
-
#
-
# require "test_helper"
-
#
-
# class WebSiteTest < ActiveSupport::TestCase
-
# test "web_site_count" do
-
# assert_equal 2, WebSite.count
-
# end
-
# end
-
#
-
# By default, +test_helper.rb+ will load all of your fixtures into your test
-
# database, so this test will succeed.
-
#
-
# The testing environment will automatically load all the fixtures into the database before each
-
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
-
#
-
# In addition to being available in the database, the fixture's data may also be accessed by
-
# using a special dynamic method, which has the same name as the model.
-
#
-
# Passing in a fixture name to this dynamic method returns the fixture matching this name:
-
#
-
# test "find one" do
-
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
-
# end
-
#
-
# Passing in multiple fixture names returns all fixtures matching these names:
-
#
-
# test "find all by name" do
-
# assert_equal 2, web_sites(:rubyonrails, :google).length
-
# end
-
#
-
# Passing in no arguments returns all fixtures:
-
#
-
# test "find all" do
-
# assert_equal 2, web_sites.length
-
# end
-
#
-
# Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
-
#
-
# test "find by name that does not exist" do
-
# assert_raise(StandardError) { web_sites(:reddit) }
-
# end
-
#
-
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
-
# following tests:
-
#
-
# test "find_alt_method_1" do
-
# assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
-
# end
-
#
-
# test "find_alt_method_2" do
-
# assert_equal "Ruby on Rails", @rubyonrails.name
-
# end
-
#
-
# In order to use these methods to access fixtured data within your test cases, you must specify one of the
-
# following in your ActiveSupport::TestCase-derived class:
-
#
-
# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
-
# self.use_instantiated_fixtures = true
-
#
-
# - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
-
# self.use_instantiated_fixtures = :no_instances
-
#
-
# Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
-
# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
-
# large sets of fixtured data.
-
#
-
# = Dynamic fixtures with ERB
-
#
-
# Sometimes you don't care about the content of the fixtures as much as you care about the volume.
-
# In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
-
# testing, like:
-
#
-
# <% 1.upto(1000) do |i| %>
-
# fix_<%= i %>:
-
# id: <%= i %>
-
# name: guy_<%= i %>
-
# <% end %>
-
#
-
# This will create 1000 very simple fixtures.
-
#
-
# Using ERB, you can also inject dynamic values into your fixtures with inserts like
-
# <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
-
# This is however a feature to be used with some caution. The point of fixtures are that they're
-
# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
-
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
-
# in fixtures are to be considered a code smell.
-
#
-
# Helper methods defined in a fixture will not be available in other fixtures, to prevent against
-
# unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
-
# that is included in ActiveRecord::FixtureSet.context_class.
-
#
-
# - define a helper method in <tt>test_helper.rb</tt>
-
# module FixtureFileHelpers
-
# def file_sha(path)
-
# Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
-
# end
-
# end
-
# ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
-
#
-
# - use the helper method in a fixture
-
# photo:
-
# name: kitten.png
-
# sha: <%= file_sha 'files/kitten.png' %>
-
#
-
# = Transactional Tests
-
#
-
# Test cases can use begin+rollback to isolate their changes to the database instead of having to
-
# delete+insert for every test case.
-
#
-
# class FooTest < ActiveSupport::TestCase
-
# self.use_transactional_tests = true
-
#
-
# test "godzilla" do
-
# assert_not_empty Foo.all
-
# Foo.destroy_all
-
# assert_empty Foo.all
-
# end
-
#
-
# test "godzilla aftermath" do
-
# assert_not_empty Foo.all
-
# end
-
# end
-
#
-
# If you preload your test database with all fixture data (probably by running `bin/rails db:fixtures:load`)
-
# and use transactional tests, then you may omit all fixtures declarations in your test cases since
-
# all the data's already there and every case rolls back its changes.
-
#
-
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
-
# true. This will provide access to fixture data for every table that has been loaded through
-
# fixtures (depending on the value of +use_instantiated_fixtures+).
-
#
-
# When *not* to use transactional tests:
-
#
-
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
-
# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
-
# and rolled back in teardown. Thus, you won't be able to verify
-
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
-
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
-
# Use InnoDB, MaxDB, or NDB instead.
-
#
-
# = Advanced Fixtures
-
#
-
# Fixtures that don't specify an ID get some extra features:
-
#
-
# * Stable, autogenerated IDs
-
# * Label references for associations (belongs_to, has_one, has_many)
-
# * HABTM associations as inline lists
-
#
-
# There are some more advanced features available even if the id is specified:
-
#
-
# * Autofilled timestamp columns
-
# * Fixture label interpolation
-
# * Support for YAML defaults
-
#
-
# == Stable, Autogenerated IDs
-
#
-
# Here, have a monkey fixture:
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# reginald:
-
# id: 2
-
# name: Reginald the Pirate
-
#
-
# Each of these fixtures has two unique identifiers: one for the database
-
# and one for the humans. Why don't we generate the primary key instead?
-
# Hashing each fixture's label yields a consistent ID:
-
#
-
# george: # generated id: 503576764
-
# name: George the Monkey
-
#
-
# reginald: # generated id: 324201669
-
# name: Reginald the Pirate
-
#
-
# Active Record looks at the fixture's model class, discovers the correct
-
# primary key, and generates it right before inserting the fixture
-
# into the database.
-
#
-
# The generated ID for a given label is constant, so we can discover
-
# any fixture's ID without loading anything, as long as we know the label.
-
#
-
# == Label references for associations (belongs_to, has_one, has_many)
-
#
-
# Specifying foreign keys in fixtures can be very fragile, not to
-
# mention difficult to read. Since Active Record can figure out the ID of
-
# any fixture from its label, you can specify FK's by label instead of ID.
-
#
-
# === belongs_to
-
#
-
# Let's break out some more monkeys and pirates.
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# id: 1
-
# name: Reginald the Pirate
-
# monkey_id: 1
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# pirate_id: 1
-
#
-
# Add a few more monkeys and pirates and break this into multiple files,
-
# and it gets pretty hard to keep track of what's going on. Let's
-
# use labels instead of IDs:
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# name: Reginald the Pirate
-
# monkey: george
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# name: George the Monkey
-
# pirate: reginald
-
#
-
# Pow! All is made clear. Active Record reflects on the fixture's model class,
-
# finds all the +belongs_to+ associations, and allows you to specify
-
# a target *label* for the *association* (monkey: george) rather than
-
# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
-
#
-
# ==== Polymorphic belongs_to
-
#
-
# Supporting polymorphic relationships is a little bit more complicated, since
-
# Active Record needs to know what type your association is pointing at. Something
-
# like this should look familiar:
-
#
-
# ### in fruit.rb
-
#
-
# belongs_to :eater, polymorphic: true
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
# eater_id: 1
-
# eater_type: Monkey
-
#
-
# Can we do better? You bet!
-
#
-
# apple:
-
# eater: george (Monkey)
-
#
-
# Just provide the polymorphic target type and Active Record will take care of the rest.
-
#
-
# === has_and_belongs_to_many
-
#
-
# Time to give our monkey some fruit.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
#
-
# orange:
-
# id: 2
-
# name: orange
-
#
-
# grape:
-
# id: 3
-
# name: grape
-
#
-
# ### in fruits_monkeys.yml
-
#
-
# apple_george:
-
# fruit_id: 1
-
# monkey_id: 1
-
#
-
# orange_george:
-
# fruit_id: 2
-
# monkey_id: 1
-
#
-
# grape_george:
-
# fruit_id: 3
-
# monkey_id: 1
-
#
-
# Let's make the HABTM fixture go away.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# fruits: apple, orange, grape
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# name: apple
-
#
-
# orange:
-
# name: orange
-
#
-
# grape:
-
# name: grape
-
#
-
# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
-
# on George's fixture, but we could've just as easily specified a list
-
# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
-
# the fixture's model class and discovers the +has_and_belongs_to_many+
-
# associations.
-
#
-
# == Autofilled Timestamp Columns
-
#
-
# If your table/model specifies any of Active Record's
-
# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
-
# they will automatically be set to <tt>Time.now</tt>.
-
#
-
# If you've set specific values, they'll be left alone.
-
#
-
# == Fixture label interpolation
-
#
-
# The label of the current fixture is always available as a column value:
-
#
-
# geeksomnia:
-
# name: Geeksomnia's Account
-
# subdomain: $LABEL
-
# email: $LABEL@email.com
-
#
-
# Also, sometimes (like when porting older join table fixtures) you'll need
-
# to be able to get a hold of the identifier for a given label. ERB
-
# to the rescue:
-
#
-
# george_reginald:
-
# monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
-
# pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
-
#
-
# == Support for YAML defaults
-
#
-
# You can set and reuse defaults in your fixtures YAML file.
-
# This is the same technique used in the +database.yml+ file to specify
-
# defaults:
-
#
-
# DEFAULTS: &DEFAULTS
-
# created_on: <%= 3.weeks.ago.to_s(:db) %>
-
#
-
# first:
-
# name: Smurf
-
# <<: *DEFAULTS
-
#
-
# second:
-
# name: Fraggle
-
# <<: *DEFAULTS
-
#
-
# Any fixture labeled "DEFAULTS" is safely ignored.
-
#
-
# Besides using "DEFAULTS", you can also specify what fixtures will
-
# be ignored by setting "ignore" in "_fixture" section.
-
#
-
# # users.yml
-
# _fixture:
-
# ignore:
-
# - base
-
# # or use "ignore: base" when there is only one fixture needs to be ignored.
-
#
-
# base: &base
-
# admin: false
-
# introduction: "This is a default description"
-
#
-
# admin:
-
# <<: *base
-
# admin: true
-
#
-
# visitor:
-
# <<: *base
-
#
-
# In the above example, 'base' will be ignored when creating fixtures.
-
# This can be used for common attributes inheriting.
-
#
-
# == Configure the fixture model class
-
#
-
# It's possible to set the fixture's model class directly in the YAML file.
-
# This is helpful when fixtures are loaded outside tests and
-
# +set_fixture_class+ is not available (e.g.
-
# when running <tt>bin/rails db:fixtures:load</tt>).
-
#
-
# _fixture:
-
# model_class: User
-
# david:
-
# name: David
-
#
-
# Any fixtures labeled "_fixture" are safely ignored.
-
3
class FixtureSet
-
#--
-
# An instance of FixtureSet is normally stored in a single YAML file and
-
# possibly in a folder with the same name.
-
#++
-
-
3
MAX_ID = 2**30 - 1
-
-
2691
@@all_cached_fixtures = Hash.new { |h, k| h[k] = {} }
-
-
3
cattr_accessor :all_loaded_fixtures, default: {}
-
-
3
class ClassCache
-
3
def initialize(class_names, config)
-
3599
@class_names = class_names.stringify_keys
-
3599
@config = config
-
-
# Remove string values that aren't constants or subclasses of AR
-
3599
@class_names.delete_if do |klass_name, klass|
-
45
!insert_class(@class_names, klass_name, klass)
-
end
-
end
-
-
3
def [](fs_name)
-
6257
@class_names.fetch(fs_name) do
-
6212
klass = default_fixture_model(fs_name, @config).safe_constantize
-
6212
insert_class(@class_names, fs_name, klass)
-
end
-
end
-
-
3
private
-
3
def insert_class(class_names, name, klass)
-
# We only want to deal with AR objects.
-
6257
if klass && klass < ActiveRecord::Base
-
5798
class_names[name] = klass
-
else
-
459
class_names[name] = nil
-
end
-
end
-
-
3
def default_fixture_model(fs_name, config)
-
6212
ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config)
-
end
-
end
-
-
3
class << self
-
3
def default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
-
6212
config.pluralize_table_names ?
-
fixture_set_name.singularize.camelize :
-
fixture_set_name.camelize
-
end
-
-
3
def default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
-
"#{ config.table_name_prefix }"\
-
"#{ fixture_set_name.tr('/', '_') }"\
-
438
"#{ config.table_name_suffix }".to_sym
-
end
-
-
3
def reset_cache
-
4915
@@all_cached_fixtures.clear
-
end
-
-
3
def cache_for_connection(connection)
-
11744
@@all_cached_fixtures[connection]
-
end
-
-
3
def fixture_is_cached?(connection, table_name)
-
6627
cache_for_connection(connection)[table_name]
-
end
-
-
3
def cached_fixtures(connection, keys_to_fetch = nil)
-
3593
if keys_to_fetch
-
3593
cache_for_connection(connection).values_at(*keys_to_fetch)
-
else
-
cache_for_connection(connection).values
-
end
-
end
-
-
3
def cache_fixtures(connection, fixtures_map)
-
1524
cache_for_connection(connection).update(fixtures_map)
-
end
-
-
3
def instantiate_fixtures(object, fixture_set, load_instances = true)
-
791
return unless load_instances
-
782
fixture_set.each do |fixture_name, fixture|
-
3522
object.instance_variable_set "@#{fixture_name}", fixture.find
-
rescue FixtureClassNotFound
-
194
nil
-
end
-
end
-
-
3
def instantiate_all_loaded_fixtures(object, load_instances = true)
-
all_loaded_fixtures.each_value do |fixture_set|
-
instantiate_fixtures(object, fixture_set, load_instances)
-
end
-
end
-
-
3
def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
-
3599
fixture_set_names = Array(fixture_set_names).map(&:to_s)
-
3599
class_names = ClassCache.new class_names, config
-
-
# FIXME: Apparently JK uses this.
-
15595
connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
-
-
3599
fixture_files_to_read = fixture_set_names.reject do |fs_name|
-
6618
fixture_is_cached?(connection.call, fs_name)
-
end
-
-
3599
if fixture_files_to_read.any?
-
1530
fixtures_map = read_and_insert(
-
fixtures_directory,
-
fixture_files_to_read,
-
class_names,
-
connection,
-
)
-
1524
cache_fixtures(connection.call, fixtures_map)
-
end
-
3593
cached_fixtures(connection.call, fixture_set_names)
-
end
-
-
# Returns a consistent, platform-independent identifier for +label+.
-
# Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
-
3
def identify(label, column_type = :integer)
-
5977
if column_type == :uuid
-
87
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
-
else
-
5890
Zlib.crc32(label.to_s) % MAX_ID
-
end
-
end
-
-
# Superclass for the evaluation contexts used by ERB fixtures.
-
3
def context_class
-
7100
@context_class ||= Class.new
-
end
-
-
3
private
-
3
def read_and_insert(fixtures_directory, fixture_files, class_names, connection) # :nodoc:
-
1530
fixtures_map = {}
-
1530
fixture_sets = fixture_files.map do |fixture_set_name|
-
6257
klass = class_names[fixture_set_name]
-
6257
fixtures_map[fixture_set_name] = new( # ActiveRecord::FixtureSet.new
-
nil,
-
fixture_set_name,
-
klass,
-
::File.join(fixtures_directory, fixture_set_name)
-
)
-
end
-
1527
update_all_loaded_fixtures(fixtures_map)
-
-
1527
insert(fixture_sets, connection)
-
-
1524
fixtures_map
-
end
-
-
3
def insert(fixture_sets, connection) # :nodoc:
-
1527
fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
-
6254
if fixture_set.model_class
-
5816
fixture_set.model_class.connection
-
else
-
438
connection.call
-
end
-
end
-
-
1527
fixture_sets_by_connection.each do |conn, set|
-
8281
table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
-
-
1533
set.each do |fixture_set|
-
6254
fixture_set.table_rows.each do |table, rows|
-
7072
table_rows_for_connection[table].unshift(*rows)
-
end
-
end
-
-
1533
conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
-
-
# Cap primary key sequences to max(pk).
-
1530
if conn.respond_to?(:reset_pk_sequence!)
-
3065
set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
-
end
-
end
-
end
-
-
3
def update_all_loaded_fixtures(fixtures_map) # :nodoc:
-
1527
all_loaded_fixtures.update(fixtures_map)
-
end
-
end
-
-
3
attr_reader :table_name, :name, :fixtures, :model_class, :ignored_fixtures, :config
-
-
3
def initialize(_, name, class_name, path, config = ActiveRecord::Base)
-
6290
@name = name
-
6290
@path = path
-
6290
@config = config
-
-
6290
self.model_class = class_name
-
-
6290
@fixtures = read_fixture_files(path)
-
-
6278
@table_name = model_class&.table_name || self.class.default_fixture_table_name(name, config)
-
end
-
-
3
def [](x)
-
13129
fixtures[x]
-
end
-
-
3
def []=(k, v)
-
fixtures[k] = v
-
end
-
-
3
def each(&block)
-
821
fixtures.each(&block)
-
end
-
-
3
def size
-
fixtures.size
-
end
-
-
# Returns a hash of rows to be inserted. The key is the table, the value is
-
# a list of rows to insert to that table.
-
3
def table_rows
-
# allow specifying fixtures to be ignored by setting `ignore` in `_fixture` section
-
6269
fixtures.except!(*ignored_fixtures)
-
-
TableRows.new(
-
table_name,
-
model_class: model_class,
-
fixtures: fixtures,
-
config: config,
-
6269
).to_hash
-
end
-
-
3
private
-
3
def model_class=(class_name)
-
6308
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
-
5831
@model_class = class_name
-
else
-
477
@model_class = class_name.safe_constantize if class_name
-
end
-
end
-
-
3
def ignored_fixtures=(base)
-
6278
@ignored_fixtures =
-
case base
-
when Array
-
6
base
-
when String
-
114
[base]
-
else
-
6158
[]
-
end
-
-
6278
@ignored_fixtures << "DEFAULTS" unless @ignored_fixtures.include?("DEFAULTS")
-
6278
@ignored_fixtures.compact
-
end
-
-
# Loads the fixtures from the YAML file at +path+.
-
# If the file sets the +model_class+ and current instance value is not set,
-
# it uses the file value.
-
3
def read_fixture_files(path)
-
6290
yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
-
762
::File.file?(f)
-
} + [yaml_file_path(path)]
-
-
6290
yaml_files.each_with_object({}) do |file, fixtures|
-
7052
FixtureSet::File.open(file) do |fh|
-
7052
self.model_class ||= fh.model_class if fh.model_class
-
7040
self.ignored_fixtures ||= fh.ignored_fixtures
-
7040
fh.each do |fixture_name, row|
-
294309
fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
-
end
-
end
-
end
-
end
-
-
3
def yaml_file_path(path)
-
6290
"#{path}.yml"
-
end
-
end
-
-
3
class Fixture #:nodoc:
-
3
include Enumerable
-
-
3
class FixtureError < StandardError #:nodoc:
-
end
-
-
3
class FormatError < FixtureError #:nodoc:
-
end
-
-
3
attr_reader :model_class, :fixture
-
-
3
def initialize(fixture, model_class)
-
294309
@fixture = fixture
-
294309
@model_class = model_class
-
end
-
-
3
def class_name
-
model_class.name if model_class
-
end
-
-
3
def each
-
945
fixture.each { |item| yield item }
-
end
-
-
3
def [](key)
-
325
fixture[key]
-
end
-
-
3
alias :to_hash :fixture
-
-
3
def find
-
9083
raise FixtureClassNotFound, "No class attached to find." unless model_class
-
8886
model_class.unscoped do
-
8886
model_class.find(fixture[model_class.primary_key])
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# Returns the version of the currently loaded Active Record as a <tt>Gem::Version</tt>
-
3
def self.gem_version
-
Gem::Version.new VERSION::STRING
-
end
-
-
3
module VERSION
-
3
MAJOR = 6
-
3
MINOR = 1
-
3
TINY = 0
-
3
PRE = "alpha"
-
-
3
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/hash/indifferent_access"
-
-
3
module ActiveRecord
-
# == Single table inheritance
-
#
-
# Active Record allows inheritance by storing the name of the class in a column that by
-
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
-
# This means that an inheritance looking like this:
-
#
-
# class Company < ActiveRecord::Base; end
-
# class Firm < Company; end
-
# class Client < Company; end
-
# class PriorityClient < Client; end
-
#
-
# When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
-
# the companies table with type = "Firm". You can then fetch this row again using
-
# <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
-
#
-
# Be aware that because the type column is an attribute on the record every new
-
# subclass will instantly be marked as dirty and the type column will be included
-
# in the list of changed attributes on the record. This is different from non
-
# Single Table Inheritance(STI) classes:
-
#
-
# Company.new.changed? # => false
-
# Firm.new.changed? # => true
-
# Firm.new.changes # => {"type"=>["","Firm"]}
-
#
-
# If you don't have a type column defined in your table, single-table inheritance won't
-
# be triggered. In that case, it'll work just like normal subclasses with no special magic
-
# for differentiating between them or reloading the right type with find.
-
#
-
# Note, all the attributes for all the cases are kept in the same table. Read more:
-
# https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
-
#
-
3
module Inheritance
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
# Determines whether to store the full constant name including namespace when using STI.
-
# This is true, by default.
-
6
class_attribute :store_full_sti_class, instance_writer: false, default: true
-
end
-
-
3
module ClassMethods
-
# Determines if one of the attributes passed in is the inheritance column,
-
# and if the inheritance column is attr accessible, it initializes an
-
# instance of the given subclass instead of the base class.
-
3
def new(attributes = nil, &block)
-
15769
if abstract_class? || self == Base
-
6
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
-
end
-
-
15763
if _has_attribute?(inheritance_column)
-
4516
subclass = subclass_from_attributes(attributes)
-
-
4489
if subclass.nil? && scope_attributes = current_scope&.scope_for_create
-
126
subclass = subclass_from_attributes(scope_attributes)
-
end
-
-
4471
if subclass.nil? && base_class?
-
3105
subclass = subclass_from_attributes(column_defaults)
-
end
-
end
-
-
15707
if subclass && subclass != self
-
54
subclass.new(attributes, &block)
-
else
-
15653
super
-
end
-
end
-
-
# Returns +true+ if this does not need STI type condition. Returns
-
# +false+ if STI type condition needs to be applied.
-
3
def descends_from_active_record?
-
2290
if self == Base
-
6
false
-
2284
elsif superclass.abstract_class?
-
57
superclass.descends_from_active_record?
-
else
-
2227
superclass == Base || !columns_hash.include?(inheritance_column)
-
end
-
end
-
-
3
def finder_needs_type_condition? #:nodoc:
-
# This is like this because benchmarking justifies the strange :false stuff
-
109841
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
-
end
-
-
# Returns the class descending directly from ActiveRecord::Base, or
-
# an abstract class, if any, in the inheritance hierarchy.
-
#
-
# If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
-
# through some arbitrarily deep hierarchy, B.base_class will return A.
-
#
-
# If B < A and C < B and if A is an abstract_class then both B.base_class
-
# and C.base_class would return B as the answer since A is an abstract_class.
-
3
def base_class
-
239027
unless self < Base
-
3
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
-
end
-
-
239024
if superclass == Base || superclass.abstract_class?
-
205796
self
-
else
-
33228
superclass.base_class
-
end
-
end
-
-
# Returns whether the class is a base class.
-
# See #base_class for more information.
-
3
def base_class?
-
27788
base_class == self
-
end
-
-
# Set this to +true+ if this is an abstract class (see
-
# <tt>abstract_class?</tt>).
-
# If you are using inheritance with Active Record and don't want a class
-
# to be considered as part of the STI hierarchy, you must set this to
-
# true.
-
# +ApplicationRecord+, for example, is generated as an abstract class.
-
#
-
# Consider the following default behaviour:
-
#
-
# Shape = Class.new(ActiveRecord::Base)
-
# Polygon = Class.new(Shape)
-
# Square = Class.new(Polygon)
-
#
-
# Shape.table_name # => "shapes"
-
# Polygon.table_name # => "shapes"
-
# Square.table_name # => "shapes"
-
# Shape.create! # => #<Shape id: 1, type: nil>
-
# Polygon.create! # => #<Polygon id: 2, type: "Polygon">
-
# Square.create! # => #<Square id: 3, type: "Square">
-
#
-
# However, when using <tt>abstract_class</tt>, +Shape+ is omitted from
-
# the hierarchy:
-
#
-
# class Shape < ActiveRecord::Base
-
# self.abstract_class = true
-
# end
-
# Polygon = Class.new(Shape)
-
# Square = Class.new(Polygon)
-
#
-
# Shape.table_name # => nil
-
# Polygon.table_name # => "polygons"
-
# Square.table_name # => "polygons"
-
# Shape.create! # => NotImplementedError: Shape is an abstract class and cannot be instantiated.
-
# Polygon.create! # => #<Polygon id: 1, type: nil>
-
# Square.create! # => #<Square id: 2, type: "Square">
-
#
-
# Note that in the above example, to disallow the creation of a plain
-
# +Polygon+, you should use <tt>validates :type, presence: true</tt>,
-
# instead of setting it as an abstract class. This way, +Polygon+ will
-
# stay in the hierarchy, and Active Record will continue to correctly
-
# derive the table name.
-
3
attr_accessor :abstract_class
-
-
# Returns whether this class is an abstract class or not.
-
3
def abstract_class?
-
135856
defined?(@abstract_class) && @abstract_class == true
-
end
-
-
# Returns the value to be stored in the inheritance column for STI.
-
3
def sti_name
-
22178
store_full_sti_class ? name : name.demodulize
-
end
-
-
# Returns the class for the provided +type_name+.
-
#
-
# It is used to find the class correspondent to the value stored in the inheritance column.
-
3
def sti_class_for(type_name)
-
19668
if store_full_sti_class
-
19593
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
75
compute_type(type_name)
-
end
-
rescue NameError
-
30
raise SubclassNotFound,
-
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
-
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
-
"Please rename this column if you didn't intend it to be used for storing the inheritance class " \
-
"or overwrite #{name}.inheritance_column to use another column for that information."
-
end
-
-
# Returns the value to be stored in the polymorphic type column for Polymorphic Associations.
-
3
def polymorphic_name
-
2421
base_class.name
-
end
-
-
# Returns the class for the provided +name+.
-
#
-
# It is used to find the class correspondent to the value stored in the polymorphic type column.
-
3
def polymorphic_class_for(name)
-
3325
name.constantize
-
end
-
-
3
def inherited(subclass)
-
2940
subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
-
2940
super
-
end
-
-
3
protected
-
# Returns the class type of the record using the current module as a prefix. So descendants of
-
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
-
3
def compute_type(type_name)
-
2891
if type_name.start_with?("::")
-
# If the type is prefixed with a scope operator then we assume that
-
# the type_name is an absolute reference.
-
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
2891
type_candidate = @_type_candidates_cache[type_name]
-
2891
if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate)
-
1277
return type_constant
-
end
-
-
# Build a list of candidates to search for
-
1614
candidates = []
-
3475
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
-
1614
candidates << type_name
-
-
1614
candidates.each do |candidate|
-
3286
constant = ActiveSupport::Dependencies.safe_constantize(candidate)
-
3277
if candidate == constant.to_s
-
1596
@_type_candidates_cache[type_name] = candidate
-
1596
return constant
-
end
-
end
-
-
9
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
-
end
-
end
-
-
3
private
-
# Called by +instantiate+ to decide which class to use for a new
-
# record instance. For single-table inheritance, we check the record
-
# for a +type+ column and return the corresponding class.
-
3
def discriminate_class_for_record(record)
-
27284
if using_single_table_inheritance?(record)
-
19512
find_sti_class(record[inheritance_column])
-
else
-
7772
super
-
end
-
end
-
-
3
def using_single_table_inheritance?(record)
-
27284
record[inheritance_column].present? && _has_attribute?(inheritance_column)
-
end
-
-
3
def find_sti_class(type_name)
-
19668
type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
-
19668
subclass = sti_class_for(type_name)
-
-
19638
unless subclass == self || descendants.include?(subclass)
-
24
raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
-
end
-
-
19614
subclass
-
end
-
-
3
def type_condition(table = arel_table)
-
7473
sti_column = table[inheritance_column]
-
7473
sti_names = ([self] + descendants).map(&:sti_name)
-
-
7473
predicate_builder.build(sti_column, sti_names)
-
end
-
-
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
-
# is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
-
3
def subclass_from_attributes(attrs)
-
7747
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
-
7747
if attrs.is_a?(Hash)
-
6571
subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym]
-
-
6571
if subclass_name.present?
-
156
find_sti_class(subclass_name)
-
end
-
end
-
end
-
end
-
-
3
def initialize_dup(other)
-
111
super
-
111
ensure_proper_type
-
end
-
-
3
private
-
3
def initialize_internals_callback
-
15758
super
-
15758
ensure_proper_type
-
end
-
-
# Sets the attribute used for single table inheritance to this class name if this is not the
-
# ActiveRecord::Base descendant.
-
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
-
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
-
# No such attribute would be set for objects of the Message class in that example.
-
3
def ensure_proper_type
-
15869
klass = self.class
-
15869
if klass.finder_needs_type_condition?
-
1372
_write_attribute(klass.inheritance_column, klass.sti_name)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
-
3
module ActiveRecord
-
3
class InsertAll # :nodoc:
-
3
attr_reader :model, :connection, :inserts, :keys
-
3
attr_reader :on_duplicate, :returning, :unique_by
-
-
3
def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
-
182
raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
-
-
179
@model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
-
179
@on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
-
-
179
if model.scope_attributes?
-
18
@scope_attributes = model.scope_attributes
-
18
@keys |= @scope_attributes.keys
-
end
-
179
@keys = @keys.to_set
-
-
179
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
-
179
@returning = false if @returning == []
-
-
179
@unique_by = find_unique_index_for(unique_by)
-
158
@on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
-
-
158
ensure_valid_options_for_connection!
-
end
-
-
3
def execute
-
158
message = +"#{model} "
-
158
message << "Bulk " if inserts.many?
-
158
message << (on_duplicate == :update ? "Upsert" : "Insert")
-
158
connection.exec_insert_all to_sql, message
-
end
-
-
3
def updatable_columns
-
335
keys - readonly_columns - unique_by_columns
-
end
-
-
3
def primary_keys
-
591
Array(connection.schema_cache.primary_keys(model.table_name))
-
end
-
-
-
3
def skip_duplicates?
-
313
on_duplicate == :skip
-
end
-
-
3
def update_duplicates?
-
448
on_duplicate == :update
-
end
-
-
3
def map_key_with_value
-
155
inserts.map do |attributes|
-
211
attributes = attributes.stringify_keys
-
211
attributes.merge!(scope_attributes) if scope_attributes
-
-
211
verify_attributes(attributes)
-
-
211
keys.map do |key|
-
506
yield key, attributes[key]
-
end
-
end
-
end
-
-
3
private
-
3
attr_reader :scope_attributes
-
-
3
def find_unique_index_for(unique_by)
-
179
name_or_columns = unique_by || model.primary_key
-
179
match = Array(name_or_columns).map(&:to_s)
-
-
625
if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
-
28
index
-
151
elsif match == primary_keys
-
130
unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
-
else
-
21
raise ArgumentError, "No unique index found for #{name_or_columns}"
-
end
-
end
-
-
3
def unique_indexes
-
179
connection.schema_cache.indexes(model.table_name).select(&:unique)
-
end
-
-
-
3
def ensure_valid_options_for_connection!
-
158
if returning && !connection.supports_insert_returning?
-
raise ArgumentError, "#{connection.class} does not support :returning"
-
end
-
-
158
if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
-
raise ArgumentError, "#{connection.class} does not support skipping duplicates"
-
end
-
-
158
if update_duplicates? && !connection.supports_insert_on_duplicate_update?
-
raise ArgumentError, "#{connection.class} does not support upsert"
-
end
-
-
158
if unique_by && !connection.supports_insert_conflict_target?
-
raise ArgumentError, "#{connection.class} does not support :unique_by"
-
end
-
end
-
-
-
3
def to_sql
-
158
connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self))
-
end
-
-
-
3
def readonly_columns
-
335
primary_keys + model.readonly_attributes.to_a
-
end
-
-
3
def unique_by_columns
-
335
Array(unique_by&.columns)
-
end
-
-
-
3
def verify_attributes(attributes)
-
211
if keys != attributes.keys.to_set
-
raise ArgumentError, "All objects being inserted must have the same keys"
-
end
-
end
-
-
3
class Builder # :nodoc:
-
3
attr_reader :model
-
-
3
delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
-
-
3
def initialize(insert_all)
-
158
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
-
end
-
-
3
def into
-
158
"INTO #{model.quoted_table_name} (#{columns_list})"
-
end
-
-
3
def values_list
-
158
types = extract_types_from_columns_on(model.table_name, keys: keys)
-
-
155
values_list = insert_all.map_key_with_value do |key, value|
-
506
connection.with_yaml_fallback(types[key].serialize(value))
-
end
-
-
155
connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
-
end
-
-
3
def returning
-
107
format_columns(insert_all.returning) if insert_all.returning
-
end
-
-
3
def conflict_target
-
121
if index = insert_all.unique_by
-
34
sql = +"(#{format_columns(index.columns)})"
-
34
sql << " WHERE #{index.where}" if index.where
-
34
sql
-
87
elsif update_duplicates?
-
45
"(#{format_columns(insert_all.primary_keys)})"
-
end
-
end
-
-
3
def updatable_columns
-
163
quote_columns(insert_all.updatable_columns)
-
end
-
-
3
def touch_model_timestamps_unless(&block)
-
model.send(:timestamp_attributes_for_update_in_model).map do |column_name|
-
108
if touch_timestamp_attribute?(column_name)
-
102
"#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE CURRENT_TIMESTAMP END),"
-
end
-
61
end.compact.join
-
end
-
-
3
private
-
3
attr_reader :connection, :insert_all
-
-
3
def touch_timestamp_attribute?(column_name)
-
108
update_duplicates? && !insert_all.updatable_columns.include?(column_name)
-
end
-
-
3
def columns_list
-
158
format_columns(insert_all.keys)
-
end
-
-
3
def extract_types_from_columns_on(table_name, keys:)
-
158
columns = connection.schema_cache.columns_hash(table_name)
-
-
158
unknown_column = (keys - columns.keys).first
-
158
raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
-
-
536
keys.index_with { |key| model.type_for_attribute(key) }
-
end
-
-
3
def format_columns(columns)
-
341
columns.respond_to?(:map) ? quote_columns(columns).join(",") : columns
-
end
-
-
3
def quote_columns(columns)
-
498
columns.map(&connection.method(:quote_column_name))
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/filters"
-
-
3
module ActiveRecord
-
3
module Integration
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
##
-
# :singleton-method:
-
# Indicates the format used to generate the timestamp in the cache key, if
-
# versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
-
#
-
# This is +:usec+, by default.
-
3
class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
-
-
##
-
# :singleton-method:
-
# Indicates whether to use a stable #cache_key method that is accompanied
-
# by a changing version in the #cache_version method.
-
#
-
# This is +true+, by default on Rails 5.2 and above.
-
3
class_attribute :cache_versioning, instance_writer: false, default: false
-
-
##
-
# :singleton-method:
-
# Indicates whether to use a stable #cache_key method that is accompanied
-
# by a changing version in the #cache_version method on collections.
-
#
-
# This is +false+, by default until Rails 6.1.
-
3
class_attribute :collection_cache_versioning, instance_writer: false, default: false
-
end
-
-
# Returns a +String+, which Action Pack uses for constructing a URL to this
-
# object. The default implementation returns this record's id as a +String+,
-
# or +nil+ if this record's unsaved.
-
#
-
# For example, suppose that you have a User model, and that you have a
-
# <tt>resources :users</tt> route. Normally, +user_path+ will
-
# construct a path with the user object's 'id' in it:
-
#
-
# user = User.find_by(name: 'Phusion')
-
# user_path(user) # => "/users/1"
-
#
-
# You can override +to_param+ in your model to make +user_path+ construct
-
# a path using the user's name instead of the user's id:
-
#
-
# class User < ActiveRecord::Base
-
# def to_param # overridden
-
# name
-
# end
-
# end
-
#
-
# user = User.find_by(name: 'Phusion')
-
# user_path(user) # => "/users/Phusion"
-
3
def to_param
-
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
-
48
id && id.to_s # Be sure to stringify the id for routes
-
end
-
-
# Returns a stable cache key that can be used to identify this record.
-
#
-
# Product.new.cache_key # => "products/new"
-
# Product.find(5).cache_key # => "products/5"
-
#
-
# If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
-
# the cache key will also include a version.
-
#
-
# Product.cache_versioning = false
-
# Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available)
-
3
def cache_key
-
69
if new_record?
-
"#{model_name.cache_key}/new"
-
else
-
69
if cache_version
-
18
"#{model_name.cache_key}/#{id}"
-
else
-
51
timestamp = max_updated_column_timestamp
-
-
51
if timestamp
-
48
timestamp = timestamp.utc.to_s(cache_timestamp_format)
-
48
"#{model_name.cache_key}/#{id}-#{timestamp}"
-
else
-
3
"#{model_name.cache_key}/#{id}"
-
end
-
end
-
end
-
end
-
-
# Returns a cache version that can be used together with the cache key to form
-
# a recyclable caching scheme. By default, the #updated_at column is used for the
-
# cache_version, but this method can be overwritten to return something else.
-
#
-
# Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
-
# +false+.
-
3
def cache_version
-
134
return unless cache_versioning
-
-
77
if has_attribute?("updated_at")
-
74
timestamp = updated_at_before_type_cast
-
74
if can_use_fast_cache_version?(timestamp)
-
22
raw_timestamp_to_cache_version(timestamp)
-
52
elsif timestamp = updated_at
-
40
timestamp.utc.to_s(cache_timestamp_format)
-
end
-
3
elsif self.class.has_attribute?("updated_at")
-
3
raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
-
end
-
end
-
-
# Returns a cache key along with the version.
-
3
def cache_key_with_version
-
12
if version = cache_version
-
9
"#{cache_key}-#{version}"
-
else
-
3
cache_key
-
end
-
end
-
-
3
module ClassMethods
-
# Defines your model's +to_param+ method to generate "pretty" URLs
-
# using +method_name+, which can be any attribute or method that
-
# responds to +to_s+.
-
#
-
# class User < ActiveRecord::Base
-
# to_param :name
-
# end
-
#
-
# user = User.find_by(name: 'Fancy Pants')
-
# user.id # => 123
-
# user_path(user) # => "/users/123-fancy-pants"
-
#
-
# Values longer than 20 characters will be truncated. The value
-
# is truncated word by word.
-
#
-
# user = User.find_by(name: 'David Heinemeier Hansson')
-
# user.id # => 125
-
# user_path(user) # => "/users/125-david-heinemeier"
-
#
-
# Because the generated param begins with the record's +id+, it is
-
# suitable for passing to +find+. In a controller, for example:
-
#
-
# params[:id] # => "123-fancy-pants"
-
# User.find(params[:id]).id # => 123
-
3
def to_param(method_name = nil)
-
6
if method_name.nil?
-
3
super()
-
else
-
3
define_method :to_param do
-
36
if (default = super()) &&
-
33
(result = send(method_name).to_s).present? &&
-
27
(param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present?
-
24
"#{default}-#{param}"
-
else
-
12
default
-
end
-
end
-
end
-
end
-
-
3
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
-
75
collection.send(:compute_cache_key, timestamp_column)
-
end
-
end
-
-
3
private
-
# Detects if the value before type cast
-
# can be used to generate a cache_version.
-
#
-
# The fast cache version only works with a
-
# string value directly from the database.
-
#
-
# We also must check if the timestamp format has been changed
-
# or if the timezone is not set to UTC then
-
# we cannot apply our transformations correctly.
-
3
def can_use_fast_cache_version?(timestamp)
-
74
timestamp.is_a?(String) &&
-
cache_timestamp_format == :usec &&
-
default_timezone == :utc &&
-
!updated_at_came_from_user?
-
end
-
-
# Converts a raw database string to `:usec`
-
# format.
-
#
-
# Example:
-
#
-
# timestamp = "2018-10-15 20:02:15.266505"
-
# raw_timestamp_to_cache_version(timestamp)
-
# # => "20181015200215266505"
-
#
-
# PostgreSQL truncates trailing zeros,
-
# https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214
-
# to account for this we pad the output with zeros
-
3
def raw_timestamp_to_cache_version(timestamp)
-
22
key = timestamp.delete("- :.")
-
22
if key.length < 20
-
4
key.ljust(20, "0")
-
else
-
18
key
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/scoping/default"
-
3
require "active_record/scoping/named"
-
-
3
module ActiveRecord
-
# This class is used to create a table that keeps track of values and keys such
-
# as which environment migrations were run in.
-
#
-
# This is enabled by default. To disable this functionality set
-
# `use_metadata_table` to false in your database configuration.
-
3
class InternalMetadata < ActiveRecord::Base # :nodoc:
-
3
class << self
-
3
def enabled?
-
806
ActiveRecord::Base.connection.use_metadata_table?
-
end
-
-
3
def _internal?
-
true
-
end
-
-
3
def primary_key
-
1606
"key"
-
end
-
-
3
def table_name
-
631
"#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
-
end
-
-
3
def []=(key, value)
-
266
return unless enabled?
-
-
263
find_or_initialize_by(key: key).update!(value: value)
-
end
-
-
3
def [](key)
-
51
return unless enabled?
-
-
48
where(key: key).pluck(:value).first
-
end
-
-
# Creates an internal metadata table with columns +key+ and +value+
-
3
def create_table
-
444
return unless enabled?
-
-
441
unless table_exists?
-
8
key_options = connection.internal_string_options_for_primary_key
-
-
8
connection.create_table(table_name, id: false) do |t|
-
8
t.string :key, **key_options
-
8
t.string :value
-
8
t.timestamps
-
end
-
end
-
end
-
-
3
def drop_table
-
3
return unless enabled?
-
-
3
connection.drop_table table_name, if_exists: true
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module LegacyYamlAdapter # :nodoc:
-
3
def self.convert(klass, coder)
-
68
return coder unless coder.is_a?(Psych::Coder)
-
-
68
case coder["active_record_yaml_version"]
-
62
when 1, 2 then coder
-
else
-
6
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
YAML loading from legacy format older than Rails 5.0 is deprecated
-
and will be removed in Rails 6.2.
-
MSG
-
6
if coder["attributes"].is_a?(ActiveModel::AttributeSet)
-
3
Rails420.convert(klass, coder)
-
else
-
3
Rails41.convert(klass, coder)
-
end
-
end
-
end
-
-
3
module Rails420 # :nodoc:
-
3
def self.convert(klass, coder)
-
3
attribute_set = coder["attributes"]
-
-
3
klass.attribute_names.each do |attr_name|
-
54
attribute = attribute_set[attr_name]
-
54
if attribute.type.is_a?(Delegator)
-
3
type_from_klass = klass.type_for_attribute(attr_name)
-
3
attribute_set[attr_name] = attribute.with_type(type_from_klass)
-
end
-
end
-
-
3
coder
-
end
-
end
-
-
3
module Rails41 # :nodoc:
-
3
def self.convert(klass, coder)
-
3
attributes = klass.attributes_builder
-
.build_from_database(coder["attributes"])
-
3
new_record = coder["attributes"][klass.primary_key].blank?
-
-
3
{
-
"attributes" => attributes,
-
"new_record" => new_record,
-
}
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Locking
-
# == What is Optimistic Locking
-
#
-
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
-
# conflicts with the data. It does this by checking whether another process has made changes to a record since
-
# it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
-
# and the update is ignored.
-
#
-
# Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
-
#
-
# == Usage
-
#
-
# Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
-
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
-
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.first_name = "should fail"
-
# p2.save # Raises an ActiveRecord::StaleObjectError
-
#
-
# Optimistic locking will also check for stale data when objects are destroyed. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.destroy # Raises an ActiveRecord::StaleObjectError
-
#
-
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
-
# or otherwise apply the business logic needed to resolve the conflict.
-
#
-
# This locking mechanism will function inside a single Ruby process. To make it work across all
-
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
-
#
-
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
-
# To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
-
#
-
# class Person < ActiveRecord::Base
-
# self.locking_column = :lock_person
-
# end
-
#
-
3
module Optimistic
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
3
class_attribute :lock_optimistically, instance_writer: false, default: true
-
end
-
-
3
def locking_enabled? #:nodoc:
-
16784
self.class.locking_enabled?
-
end
-
-
3
def increment!(*, **) #:nodoc:
-
486
super.tap do
-
486
if locking_enabled?
-
33
self[self.class.locking_column] += 1
-
33
clear_attribute_change(self.class.locking_column)
-
end
-
end
-
end
-
-
3
private
-
3
def _create_record(attribute_names = self.attribute_names)
-
12437
if locking_enabled?
-
# We always want to persist the locking version, even if we don't detect
-
# a change from the default, since the database might have no default
-
1035
attribute_names |= [self.class.locking_column]
-
end
-
12437
super
-
end
-
-
3
def _touch_row(attribute_names, time)
-
504
@_touch_attr_names << self.class.locking_column if locking_enabled?
-
504
super
-
end
-
-
3
def _update_row(attribute_names, attempted_action = "update")
-
2399
return super unless locking_enabled?
-
-
355
begin
-
355
locking_column = self.class.locking_column
-
355
previous_lock_value = read_attribute_before_type_cast(locking_column)
-
355
attribute_names = attribute_names.dup if attribute_names.frozen?
-
355
attribute_names << locking_column
-
-
355
self[locking_column] += 1
-
-
355
affected_rows = self.class._update_record(
-
attributes_with_values(attribute_names),
-
@primary_key => id_in_database,
-
locking_column => @attributes[locking_column].original_value_for_database
-
)
-
-
352
if affected_rows != 1
-
45
raise ActiveRecord::StaleObjectError.new(self, attempted_action)
-
end
-
-
307
affected_rows
-
-
# If something went wrong, revert the locking_column value.
-
48
rescue Exception
-
48
self[locking_column] = previous_lock_value.to_i
-
48
raise
-
end
-
end
-
-
3
def destroy_row
-
958
return super unless locking_enabled?
-
-
72
locking_column = self.class.locking_column
-
-
72
affected_rows = self.class._delete_record(
-
@primary_key => id_in_database,
-
locking_column => read_attribute_before_type_cast(locking_column)
-
)
-
-
72
if affected_rows != 1
-
9
raise ActiveRecord::StaleObjectError.new(self, "destroy")
-
end
-
-
63
affected_rows
-
end
-
-
3
module ClassMethods
-
3
DEFAULT_LOCKING_COLUMN = "lock_version"
-
-
# Returns true if the +lock_optimistically+ flag is set to true
-
# (which it is, by default) and the table includes the
-
# +locking_column+ column (defaults to +lock_version+).
-
3
def locking_enabled?
-
18668
lock_optimistically && columns_hash[locking_column]
-
end
-
-
# Set the column to use for optimistic locking. Defaults to +lock_version+.
-
3
def locking_column=(value)
-
15
reload_schema_from_cache
-
15
@locking_column = value.to_s
-
end
-
-
# The version column used for optimistic locking. Defaults to +lock_version+.
-
3
def locking_column
-
50754
@locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
-
50754
@locking_column
-
end
-
-
# Reset the column used for optimistic locking back to the +lock_version+ default.
-
3
def reset_locking_column
-
self.locking_column = DEFAULT_LOCKING_COLUMN
-
end
-
-
# Make sure the lock version column gets updated when counters are
-
# updated.
-
3
def update_counters(id, counters)
-
618
counters = counters.merge(locking_column => 1) if locking_enabled?
-
618
super
-
end
-
-
3
def define_attribute(name, cast_type, **) # :nodoc:
-
30036
if lock_optimistically && name == locking_column
-
350
cast_type = LockingType.new(cast_type)
-
end
-
30036
super
-
end
-
end
-
end
-
-
# In de/serialize we change `nil` to 0, so that we can allow passing
-
# `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
-
# during update record.
-
3
class LockingType < DelegateClass(Type::Value) # :nodoc:
-
3
def self.new(subtype)
-
350
self === subtype ? subtype : super
-
end
-
-
3
def deserialize(value)
-
1721
super.to_i
-
end
-
-
3
def serialize(value)
-
3324
super.to_i
-
end
-
-
3
def init_with(coder)
-
__setobj__(coder["subtype"])
-
end
-
-
3
def encode_with(coder)
-
coder["subtype"] = __getobj__
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Locking
-
# Locking::Pessimistic provides support for row-level locking using
-
# SELECT ... FOR UPDATE and other lock types.
-
#
-
# Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
-
# lock on the selected rows:
-
# # select * from accounts where id=1 for update
-
# Account.lock.find(1)
-
#
-
# Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
-
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where name = 'shugo' limit 1 for update nowait
-
# shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
-
# yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
-
# shugo.balance -= 100
-
# shugo.save!
-
# yuko.balance += 100
-
# yuko.save!
-
# end
-
#
-
# You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
-
# This may be better if you don't need to lock every row. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where ...
-
# accounts = Account.where(...)
-
# account1 = accounts.detect { |account| ... }
-
# account2 = accounts.detect { |account| ... }
-
# # select * from accounts where id=? for update
-
# account1.lock!
-
# account2.lock!
-
# account1.balance -= 100
-
# account1.save!
-
# account2.balance += 100
-
# account2.save!
-
# end
-
#
-
# You can start a transaction and acquire the lock in one go by calling
-
# <tt>with_lock</tt> with a block. The block is called from within
-
# a transaction, the object is already locked. Example:
-
#
-
# account = Account.first
-
# account.with_lock do
-
# # This block is called within a transaction,
-
# # account is already locked.
-
# account.balance -= 100
-
# account.save!
-
# end
-
#
-
# Database-specific information on row locking:
-
#
-
# [MySQL]
-
# https://dev.mysql.com/doc/refman/en/innodb-locking-reads.html
-
#
-
# [PostgreSQL]
-
# https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
-
3
module Pessimistic
-
# Obtain a row lock on this record. Reloads the record to obtain the requested
-
# lock. Pass an SQL locking clause to append the end of the SELECT statement
-
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
-
# the locked record.
-
3
def lock!(lock = true)
-
25
if persisted?
-
25
if has_changes_to_save?
-
2
raise(<<-MSG.squish)
-
Locking a record with unpersisted changes is not supported. Use
-
`save` to persist the changes, or `reload` to discard them
-
explicitly.
-
MSG
-
end
-
-
23
reload(lock: lock)
-
end
-
23
self
-
end
-
-
# Wraps the passed block in a transaction, locking the object
-
# before yielding. You can pass the SQL locking clause
-
# as argument (see <tt>lock!</tt>).
-
3
def with_lock(lock = true)
-
12
transaction do
-
12
lock!(lock)
-
12
yield
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class LogSubscriber < ActiveSupport::LogSubscriber
-
3
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
-
-
3
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
-
-
3
def self.runtime=(value)
-
428212
ActiveRecord::RuntimeRegistry.sql_runtime = value
-
end
-
-
3
def self.runtime
-
428215
ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
-
end
-
-
3
def self.reset_runtime
-
rt, self.runtime = runtime, 0
-
rt
-
end
-
-
3
def sql(event)
-
428212
self.class.runtime += event.duration
-
428212
return unless logger.debug?
-
-
428188
payload = event.payload
-
-
428188
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
-
-
298245
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
-
298245
name = "CACHE #{name}" if payload[:cached]
-
298245
sql = payload[:sql]
-
298245
binds = nil
-
-
298245
if payload[:binds]&.any?
-
50631
casted_params = type_casted_binds(payload[:type_casted_binds])
-
-
50631
binds = []
-
50631
payload[:binds].each_with_index do |attr, i|
-
309086
binds << render_bind(attr, casted_params[i])
-
end
-
50631
binds = binds.inspect
-
50631
binds.prepend(" ")
-
end
-
-
298245
name = colorize_payload_name(name, payload[:name])
-
298245
sql = color(sql, sql_color(sql), true) if colorize_logging
-
-
298245
debug " #{name} #{sql}#{binds}"
-
end
-
-
3
private
-
3
def type_casted_binds(casted_binds)
-
50631
casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
-
end
-
-
3
def render_bind(attr, value)
-
309086
case attr
-
when ActiveModel::Attribute
-
112784
if attr.type.binary? && attr.value
-
34
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
-
end
-
when Array
-
18
attr = attr.first
-
else
-
196284
attr = nil
-
end
-
-
309086
[attr&.name, value]
-
end
-
-
3
def colorize_payload_name(name, payload_name)
-
298245
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
-
57996
color(name, MAGENTA, true)
-
else
-
240249
color(name, CYAN, true)
-
end
-
end
-
-
3
def sql_color(sql)
-
238594
case sql
-
when /\A\s*rollback/mi
-
60748
RED
-
when /select .*for update/mi, /\A\s*lock/mi
-
43
WHITE
-
when /\A\s*select/i
-
33982
BLUE
-
when /\A\s*insert/i
-
13159
GREEN
-
when /\A\s*update/i
-
3542
YELLOW
-
when /\A\s*delete/i
-
4464
RED
-
when /transaction\s*\Z/i
-
41011
CYAN
-
else
-
81645
MAGENTA
-
end
-
end
-
-
3
def logger
-
1880711
ActiveRecord::Base.logger
-
end
-
-
3
def debug(progname = nil, &block)
-
298239
return unless super
-
-
298239
if ActiveRecord::Base.verbose_query_logs
-
6
log_query_source
-
end
-
end
-
-
3
def log_query_source
-
6
source = extract_query_source_location(caller)
-
-
6
if source
-
3
logger.debug(" ↳ #{source}")
-
end
-
end
-
-
3
def extract_query_source_location(locations)
-
3
backtrace_cleaner.clean(locations.lazy).first
-
end
-
end
-
end
-
-
3
ActiveRecord::LogSubscriber.attach_to :active_record
-
# frozen_string_literal: true
-
-
3
require "active_record/middleware/database_selector/resolver"
-
-
3
module ActiveRecord
-
3
module Middleware
-
# The DatabaseSelector Middleware provides a framework for automatically
-
# swapping from the primary to the replica database connection. Rails
-
# provides a basic framework to determine when to swap and allows for
-
# applications to write custom strategy classes to override the default
-
# behavior.
-
#
-
# The resolver class defines when the application should switch (i.e. read
-
# from the primary if a write occurred less than 2 seconds ago) and a
-
# resolver context class that sets a value that helps the resolver class
-
# decide when to switch.
-
#
-
# Rails default middleware uses the request's session to set a timestamp
-
# that informs the application when to read from a primary or read from a
-
# replica.
-
#
-
# To use the DatabaseSelector in your application with default settings add
-
# the following options to your environment config:
-
#
-
# config.active_record.database_selector = { delay: 2.seconds }
-
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
-
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
-
#
-
# New applications will include these lines commented out in the production.rb.
-
#
-
# The default behavior can be changed by setting the config options to a
-
# custom class:
-
#
-
# config.active_record.database_selector = { delay: 2.seconds }
-
# config.active_record.database_resolver = MyResolver
-
# config.active_record.database_resolver_context = MyResolver::MySession
-
3
class DatabaseSelector
-
3
def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
-
6
@app = app
-
6
@resolver_klass = resolver_klass || Resolver
-
6
@context_klass = context_klass || Resolver::Session
-
6
@options = options
-
end
-
-
3
attr_reader :resolver_klass, :context_klass, :options
-
-
# Middleware that determines which database connection to use in a multiple
-
# database application.
-
3
def call(env)
-
6
request = ActionDispatch::Request.new(env)
-
-
6
select_database(request) do
-
6
@app.call(env)
-
end
-
end
-
-
3
private
-
3
def select_database(request, &blk)
-
6
context = context_klass.call(request)
-
6
resolver = resolver_klass.call(context, options)
-
-
6
response = if reading_request?(request)
-
3
resolver.read(&blk)
-
else
-
3
resolver.write(&blk)
-
end
-
-
6
resolver.update_context(response)
-
6
response
-
end
-
-
3
def reading_request?(request)
-
6
request.get? || request.head?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/middleware/database_selector/resolver/session"
-
3
require "active_support/core_ext/numeric/time"
-
-
3
module ActiveRecord
-
3
module Middleware
-
3
class DatabaseSelector
-
# The Resolver class is used by the DatabaseSelector middleware to
-
# determine which database the request should use.
-
#
-
# To change the behavior of the Resolver class in your application,
-
# create a custom resolver class that inherits from
-
# DatabaseSelector::Resolver and implements the methods that need to
-
# be changed.
-
#
-
# By default the Resolver class will send read traffic to the replica
-
# if it's been 2 seconds since the last write.
-
3
class Resolver # :nodoc:
-
3
SEND_TO_REPLICA_DELAY = 2.seconds
-
-
3
def self.call(context, options = {})
-
6
new(context, options)
-
end
-
-
3
def initialize(context, options = {})
-
35
@context = context
-
35
@options = options
-
35
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
-
35
@instrumenter = ActiveSupport::Notifications.instrumenter
-
end
-
-
3
attr_reader :context, :delay, :instrumenter
-
-
3
def read(&blk)
-
26
if read_from_primary?
-
15
read_from_primary(&blk)
-
else
-
11
read_from_replica(&blk)
-
end
-
end
-
-
3
def write(&blk)
-
27
write_to_primary(&blk)
-
end
-
-
3
def update_context(response)
-
9
context.save(response)
-
end
-
-
3
private
-
3
def read_from_primary(&blk)
-
15
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
-
15
instrumenter.instrument("database_selector.active_record.read_from_primary") do
-
15
yield
-
end
-
end
-
end
-
-
3
def read_from_replica(&blk)
-
11
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
-
11
instrumenter.instrument("database_selector.active_record.read_from_replica") do
-
11
yield
-
end
-
end
-
end
-
-
3
def write_to_primary(&blk)
-
27
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
-
27
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
-
27
yield
-
ensure
-
27
context.update_last_write_timestamp
-
end
-
end
-
end
-
-
3
def read_from_primary?
-
26
!time_since_last_write_ok?
-
end
-
-
3
def send_to_replica_delay
-
26
delay
-
end
-
-
3
def time_since_last_write_ok?
-
26
Time.now - context.last_write_timestamp >= send_to_replica_delay
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Middleware
-
3
class DatabaseSelector
-
3
class Resolver
-
# The session class is used by the DatabaseSelector::Resolver to save
-
# timestamps of the last write in the session.
-
#
-
# The last_write is used to determine whether it's safe to read
-
# from the replica or the request needs to be sent to the primary.
-
3
class Session # :nodoc:
-
3
def self.call(request)
-
6
new(request.session)
-
end
-
-
# Converts time to a timestamp that represents milliseconds since
-
# epoch.
-
3
def self.convert_time_to_timestamp(time)
-
47
time.to_i * 1000 + time.usec / 1000
-
end
-
-
# Converts milliseconds since epoch timestamp into a time object.
-
3
def self.convert_timestamp_to_time(timestamp)
-
41
timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
-
end
-
-
3
def initialize(session)
-
56
@session = session
-
end
-
-
3
attr_reader :session
-
-
3
def last_write_timestamp
-
41
self.class.convert_timestamp_to_time(session[:last_write])
-
end
-
-
3
def update_last_write_timestamp
-
39
session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
-
end
-
-
3
def save(response)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "benchmark"
-
3
require "set"
-
3
require "zlib"
-
3
require "active_support/core_ext/array/access"
-
3
require "active_support/core_ext/enumerable"
-
3
require "active_support/core_ext/module/attribute_accessors"
-
3
require "active_support/actionable_error"
-
-
3
module ActiveRecord
-
3
class MigrationError < ActiveRecordError #:nodoc:
-
3
def initialize(message = nil)
-
101
message = "\n\n#{message}\n\n" if message
-
101
super
-
end
-
end
-
-
# Exception that can be raised to stop migrations from being rolled back.
-
# For example the following migration is not reversible.
-
# Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error.
-
#
-
# class IrreversibleMigrationExample < ActiveRecord::Migration[6.0]
-
# def change
-
# create_table :distributors do |t|
-
# t.string :zipcode
-
# end
-
#
-
# execute <<~SQL
-
# ALTER TABLE distributors
-
# ADD CONSTRAINT zipchk
-
# CHECK (char_length(zipcode) = 5) NO INHERIT;
-
# SQL
-
# end
-
# end
-
#
-
# There are two ways to mitigate this problem.
-
#
-
# 1. Define <tt>#up</tt> and <tt>#down</tt> methods instead of <tt>#change</tt>:
-
#
-
# class ReversibleMigrationExample < ActiveRecord::Migration[6.0]
-
# def up
-
# create_table :distributors do |t|
-
# t.string :zipcode
-
# end
-
#
-
# execute <<~SQL
-
# ALTER TABLE distributors
-
# ADD CONSTRAINT zipchk
-
# CHECK (char_length(zipcode) = 5) NO INHERIT;
-
# SQL
-
# end
-
#
-
# def down
-
# execute <<~SQL
-
# ALTER TABLE distributors
-
# DROP CONSTRAINT zipchk
-
# SQL
-
#
-
# drop_table :distributors
-
# end
-
# end
-
#
-
# 2. Use the #reversible method in <tt>#change</tt> method:
-
#
-
# class ReversibleMigrationExample < ActiveRecord::Migration[6.0]
-
# def change
-
# create_table :distributors do |t|
-
# t.string :zipcode
-
# end
-
#
-
# reversible do |dir|
-
# dir.up do
-
# execute <<~SQL
-
# ALTER TABLE distributors
-
# ADD CONSTRAINT zipchk
-
# CHECK (char_length(zipcode) = 5) NO INHERIT;
-
# SQL
-
# end
-
#
-
# dir.down do
-
# execute <<~SQL
-
# ALTER TABLE distributors
-
# DROP CONSTRAINT zipchk
-
# SQL
-
# end
-
# end
-
# end
-
# end
-
3
class IrreversibleMigration < MigrationError
-
end
-
-
3
class DuplicateMigrationVersionError < MigrationError #:nodoc:
-
3
def initialize(version = nil)
-
6
if version
-
3
super("Multiple migrations have the version number #{version}.")
-
else
-
3
super("Duplicate migration version error.")
-
end
-
end
-
end
-
-
3
class DuplicateMigrationNameError < MigrationError #:nodoc:
-
3
def initialize(name = nil)
-
6
if name
-
3
super("Multiple migrations have the name #{name}.")
-
else
-
3
super("Duplicate migration name.")
-
end
-
end
-
end
-
-
3
class UnknownMigrationVersionError < MigrationError #:nodoc:
-
3
def initialize(version = nil)
-
18
if version
-
15
super("No migration with version number #{version}.")
-
else
-
3
super("Unknown migration version.")
-
end
-
end
-
end
-
-
3
class IllegalMigrationNameError < MigrationError #:nodoc:
-
3
def initialize(name = nil)
-
3
if name
-
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
-
else
-
3
super("Illegal name for migration.")
-
end
-
end
-
end
-
-
3
class PendingMigrationError < MigrationError #:nodoc:
-
3
include ActiveSupport::ActionableError
-
-
3
action "Run pending migrations" do
-
1
ActiveRecord::Tasks::DatabaseTasks.migrate
-
end
-
-
3
def initialize(message = nil)
-
6
if !message && defined?(Rails.env)
-
super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate RAILS_ENV=#{::Rails.env}")
-
6
elsif !message
-
6
super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate")
-
else
-
super
-
end
-
end
-
end
-
-
3
class ConcurrentMigrationError < MigrationError #:nodoc:
-
3
DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
-
3
RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
-
-
3
def initialize(message = DEFAULT_MESSAGE)
-
6
super
-
end
-
end
-
-
3
class NoEnvironmentInSchemaError < MigrationError #:nodoc:
-
3
def initialize
-
6
msg = "Environment data not found in the schema. To resolve this issue, run: \n\n bin/rails db:environment:set"
-
6
if defined?(Rails.env)
-
super("#{msg} RAILS_ENV=#{::Rails.env}")
-
else
-
6
super(msg)
-
end
-
end
-
end
-
-
3
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
-
3
def initialize(env = "production")
-
9
msg = +"You are attempting to run a destructive action against your '#{env}' database.\n"
-
9
msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
-
9
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
-
9
super(msg)
-
end
-
end
-
-
3
class EnvironmentMismatchError < ActiveRecordError
-
3
def initialize(current: nil, stored: nil)
-
3
msg = +"You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
-
3
msg << "You are running in `#{ current }` environment. "
-
3
msg << "If you are sure you want to continue, first set the environment using:\n\n"
-
3
msg << " bin/rails db:environment:set"
-
3
if defined?(Rails.env)
-
super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
-
else
-
3
super("#{msg}\n\n")
-
end
-
end
-
end
-
-
3
class EnvironmentStorageError < ActiveRecordError # :nodoc:
-
3
def initialize
-
3
msg = +"You are attempting to store the environment in a database where metadata is disabled.\n"
-
3
msg << "Check your database configuration to see if this is intended."
-
3
super(msg)
-
end
-
end
-
-
# = Active Record Migrations
-
#
-
# Migrations can manage the evolution of a schema used by several physical
-
# databases. It's a solution to the common problem of adding a field to make
-
# a new feature work in your local database, but being unsure of how to
-
# push that change to other developers and to the production server. With
-
# migrations, you can describe the transformations in self-contained classes
-
# that can be checked into version control systems and executed against
-
# another database that might be one, two, or five versions behind.
-
#
-
# Example of a simple migration:
-
#
-
# class AddSsl < ActiveRecord::Migration[6.0]
-
# def up
-
# add_column :accounts, :ssl_enabled, :boolean, default: true
-
# end
-
#
-
# def down
-
# remove_column :accounts, :ssl_enabled
-
# end
-
# end
-
#
-
# This migration will add a boolean flag to the accounts table and remove it
-
# if you're backing out of the migration. It shows how all migrations have
-
# two methods +up+ and +down+ that describes the transformations
-
# required to implement or remove the migration. These methods can consist
-
# of both the migration specific methods like +add_column+ and +remove_column+,
-
# but may also contain regular Ruby code for generating data needed for the
-
# transformations.
-
#
-
# Example of a more complex migration that also needs to initialize data:
-
#
-
# class AddSystemSettings < ActiveRecord::Migration[6.0]
-
# def up
-
# create_table :system_settings do |t|
-
# t.string :name
-
# t.string :label
-
# t.text :value
-
# t.string :type
-
# t.integer :position
-
# end
-
#
-
# SystemSetting.create name: 'notice',
-
# label: 'Use notice?',
-
# value: 1
-
# end
-
#
-
# def down
-
# drop_table :system_settings
-
# end
-
# end
-
#
-
# This migration first adds the +system_settings+ table, then creates the very
-
# first row in it using the Active Record model that relies on the table. It
-
# also uses the more advanced +create_table+ syntax where you can specify a
-
# complete table schema in one block call.
-
#
-
# == Available transformations
-
#
-
# === Creation
-
#
-
# * <tt>create_join_table(table_1, table_2, options)</tt>: Creates a join
-
# table having its name as the lexical order of the first two
-
# arguments. See
-
# ActiveRecord::ConnectionAdapters::SchemaStatements#create_join_table for
-
# details.
-
# * <tt>create_table(name, options)</tt>: Creates a table called +name+ and
-
# makes the table object available to a block that can then add columns to it,
-
# following the same format as +add_column+. See example above. The options hash
-
# is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
-
# table definition.
-
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
-
# to the table called +table_name+
-
# named +column_name+ specified to be one of the following types:
-
# <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
-
# <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
-
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
-
# specified by passing an +options+ hash like <tt>{ default: 11 }</tt>.
-
# Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
-
# <tt>{ limit: 50, null: false }</tt>) -- see
-
# ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
-
# * <tt>add_foreign_key(from_table, to_table, options)</tt>: Adds a new
-
# foreign key. +from_table+ is the table with the key column, +to_table+ contains
-
# the referenced primary key.
-
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
-
# with the name of the column. Other options include
-
# <tt>:name</tt>, <tt>:unique</tt> (e.g.
-
# <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
-
# (e.g. <tt>{ order: { name: :desc } }</tt>).
-
# * <tt>add_reference(:table_name, :reference_name)</tt>: Adds a new column
-
# +reference_name_id+ by default an integer. See
-
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details.
-
# * <tt>add_timestamps(table_name, options)</tt>: Adds timestamps (+created_at+
-
# and +updated_at+) columns to +table_name+.
-
#
-
# === Modification
-
#
-
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
-
# the column to a different type using the same parameters as add_column.
-
# * <tt>change_column_default(table_name, column_name, default_or_changes)</tt>:
-
# Sets a default value for +column_name+ defined by +default_or_changes+ on
-
# +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt>
-
# as +default_or_changes+ will make this change reversible in the migration.
-
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
-
# Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
-
# indicates whether the value can be +NULL+. See
-
# ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for
-
# details.
-
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
-
# the table called +name+. It makes the table object available to a block that
-
# can then add/remove columns, indexes or foreign keys to it.
-
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
-
# a column but keeps the type and content.
-
# * <tt>rename_index(table_name, old_name, new_name)</tt>: Renames an index.
-
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
-
# to +new_name+.
-
#
-
# === Deletion
-
#
-
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
-
# * <tt>drop_join_table(table_1, table_2, options)</tt>: Drops the join table
-
# specified by the given arguments.
-
# * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
-
# named +column_name+ from the table called +table_name+.
-
# * <tt>remove_columns(table_name, *column_names)</tt>: Removes the given
-
# columns from the table definition.
-
# * <tt>remove_foreign_key(from_table, to_table = nil, **options)</tt>: Removes the
-
# given foreign key from the table called +table_name+.
-
# * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
-
# specified by +column_names+.
-
# * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
-
# specified by +index_name+.
-
# * <tt>remove_reference(table_name, ref_name, options)</tt>: Removes the
-
# reference(s) on +table_name+ specified by +ref_name+.
-
# * <tt>remove_timestamps(table_name, options)</tt>: Removes the timestamp
-
# columns (+created_at+ and +updated_at+) from the table definition.
-
#
-
# == Irreversible transformations
-
#
-
# Some transformations are destructive in a manner that cannot be reversed.
-
# Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
-
# exception in their +down+ method.
-
#
-
# == Running migrations from within Rails
-
#
-
# The Rails package has several tools to help create and apply migrations.
-
#
-
# To generate a new migration, you can use
-
# bin/rails generate migration MyNewMigration
-
#
-
# where MyNewMigration is the name of your migration. The generator will
-
# create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
-
# in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
-
# UTC formatted date and time that the migration was generated.
-
#
-
# There is a special syntactic shortcut to generate migrations that add fields to a table.
-
#
-
# bin/rails generate migration add_fieldname_to_tablename fieldname:string
-
#
-
# This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
-
# class AddFieldnameToTablename < ActiveRecord::Migration[6.0]
-
# def change
-
# add_column :tablenames, :fieldname, :string
-
# end
-
# end
-
#
-
# To run migrations against the currently configured database, use
-
# <tt>bin/rails db:migrate</tt>. This will update the database by running all of the
-
# pending migrations, creating the <tt>schema_migrations</tt> table
-
# (see "About the schema_migrations table" section below) if missing. It will also
-
# invoke the db:schema:dump command, which will update your db/schema.rb file
-
# to match the structure of your database.
-
#
-
# To roll the database back to a previous migration version, use
-
# <tt>bin/rails db:rollback VERSION=X</tt> where <tt>X</tt> is the version to which
-
# you wish to downgrade. Alternatively, you can also use the STEP option if you
-
# wish to rollback last few migrations. <tt>bin/rails db:rollback STEP=2</tt> will rollback
-
# the latest two migrations.
-
#
-
# If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
-
# that step will fail and you'll have some manual work to do.
-
#
-
# == More examples
-
#
-
# Not all migrations change the schema. Some just fix the data:
-
#
-
# class RemoveEmptyTags < ActiveRecord::Migration[6.0]
-
# def up
-
# Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
-
# end
-
#
-
# def down
-
# # not much we can do to restore deleted data
-
# raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
-
# end
-
# end
-
#
-
# Others remove columns when they migrate up instead of down:
-
#
-
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[6.0]
-
# def up
-
# remove_column :items, :incomplete_items_count
-
# remove_column :items, :completed_items_count
-
# end
-
#
-
# def down
-
# add_column :items, :incomplete_items_count
-
# add_column :items, :completed_items_count
-
# end
-
# end
-
#
-
# And sometimes you need to do something in SQL not abstracted directly by migrations:
-
#
-
# class MakeJoinUnique < ActiveRecord::Migration[6.0]
-
# def up
-
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
-
# end
-
#
-
# def down
-
# execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
-
# end
-
# end
-
#
-
# == Using a model after changing its table
-
#
-
# Sometimes you'll want to add a column in a migration and populate it
-
# immediately after. In that case, you'll need to make a call to
-
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
-
# latest column data from after the new column was added. Example:
-
#
-
# class AddPeopleSalary < ActiveRecord::Migration[6.0]
-
# def up
-
# add_column :people, :salary, :integer
-
# Person.reset_column_information
-
# Person.all.each do |p|
-
# p.update_attribute :salary, SalaryCalculator.compute(p)
-
# end
-
# end
-
# end
-
#
-
# == Controlling verbosity
-
#
-
# By default, migrations will describe the actions they are taking, writing
-
# them to the console as they happen, along with benchmarks describing how
-
# long each step took.
-
#
-
# You can quiet them down by setting ActiveRecord::Migration.verbose = false.
-
#
-
# You can also insert your own messages and benchmarks by using the +say_with_time+
-
# method:
-
#
-
# def up
-
# ...
-
# say_with_time "Updating salaries..." do
-
# Person.all.each do |p|
-
# p.update_attribute :salary, SalaryCalculator.compute(p)
-
# end
-
# end
-
# ...
-
# end
-
#
-
# The phrase "Updating salaries..." would then be printed, along with the
-
# benchmark for the block when the block completes.
-
#
-
# == Timestamped Migrations
-
#
-
# By default, Rails generates migrations that look like:
-
#
-
# 20080717013526_your_migration_name.rb
-
#
-
# The prefix is a generation timestamp (in UTC).
-
#
-
# If you'd prefer to use numeric prefixes, you can turn timestamped migrations
-
# off by setting:
-
#
-
# config.active_record.timestamped_migrations = false
-
#
-
# In application.rb.
-
#
-
# == Reversible Migrations
-
#
-
# Reversible migrations are migrations that know how to go +down+ for you.
-
# You simply supply the +up+ logic, and the Migration system figures out
-
# how to execute the down commands for you.
-
#
-
# To define a reversible migration, define the +change+ method in your
-
# migration like this:
-
#
-
# class TenderloveMigration < ActiveRecord::Migration[6.0]
-
# def change
-
# create_table(:horses) do |t|
-
# t.column :content, :text
-
# t.column :remind_at, :datetime
-
# end
-
# end
-
# end
-
#
-
# This migration will create the horses table for you on the way up, and
-
# automatically figure out how to drop the table on the way down.
-
#
-
# Some commands cannot be reversed. If you care to define how to move up
-
# and down in these cases, you should define the +up+ and +down+ methods
-
# as before.
-
#
-
# If a command cannot be reversed, an
-
# <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
-
# the migration is moving down.
-
#
-
# For a list of commands that are reversible, please see
-
# <tt>ActiveRecord::Migration::CommandRecorder</tt>.
-
#
-
# == Transactional Migrations
-
#
-
# If the database adapter supports DDL transactions, all migrations will
-
# automatically be wrapped in a transaction. There are queries that you
-
# can't execute inside a transaction though, and for these situations
-
# you can turn the automatic transactions off.
-
#
-
# class ChangeEnum < ActiveRecord::Migration[6.0]
-
# disable_ddl_transaction!
-
#
-
# def up
-
# execute "ALTER TYPE model_size ADD VALUE 'new_value'"
-
# end
-
# end
-
#
-
# Remember that you can still open your own transactions, even if you
-
# are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
-
3
class Migration
-
3
autoload :CommandRecorder, "active_record/migration/command_recorder"
-
3
autoload :Compatibility, "active_record/migration/compatibility"
-
3
autoload :JoinTable, "active_record/migration/join_table"
-
-
# This must be defined before the inherited hook, below
-
3
class Current < Migration #:nodoc:
-
end
-
-
3
def self.inherited(subclass) #:nodoc:
-
289
super
-
289
if subclass.superclass == Migration
-
3
raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
-
"Please specify the Rails release the migration was written for:\n" \
-
"\n" \
-
" class #{subclass} < ActiveRecord::Migration[4.2]"
-
end
-
end
-
-
3
def self.[](version)
-
84
Compatibility.find(version)
-
end
-
-
3
def self.current_version
-
3
ActiveRecord::VERSION::STRING.to_f
-
end
-
-
3
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ #:nodoc:
-
-
# This class is used to verify that all migrations have been run before
-
# loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
-
3
class CheckPending
-
3
def initialize(app, file_watcher: ActiveSupport::FileUpdateChecker)
-
8
@app = app
-
8
@needs_check = true
-
8
@mutex = Mutex.new
-
8
@file_watcher = file_watcher
-
end
-
-
3
def call(env)
-
11
@mutex.synchronize do
-
11
@watcher ||= build_watcher do
-
9
@needs_check = true
-
9
ActiveRecord::Migration.check_pending!(connection)
-
6
@needs_check = false
-
end
-
-
11
if @needs_check
-
8
@watcher.execute
-
else
-
3
@watcher.execute_if_updated
-
end
-
end
-
-
8
@app.call(env)
-
end
-
-
3
private
-
3
def build_watcher(&block)
-
8
paths = Array(connection.migration_context.migrations_paths)
-
8
@file_watcher.new([], paths.index_with(["rb"]), &block)
-
end
-
-
3
def connection
-
17
ActiveRecord::Base.connection
-
end
-
end
-
-
3
class << self
-
3
attr_accessor :delegate #:nodoc:
-
3
attr_accessor :disable_ddl_transaction #:nodoc:
-
-
3
def nearest_delegate #:nodoc:
-
186
delegate || superclass.nearest_delegate
-
end
-
-
# Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
-
3
def check_pending!(connection = Base.connection)
-
9
raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
-
end
-
-
3
def load_schema_if_pending!
-
current_db_config = Base.connection_db_config
-
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
-
-
needs_update = !all_configs.all? do |db_config|
-
Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord::Base.schema_format)
-
end
-
-
if needs_update
-
# Roundtrip to Rake to allow plugins to hook into database initialization.
-
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
-
FileUtils.cd(root) do
-
Base.clear_all_connections!
-
system("bin/rails db:test:prepare")
-
end
-
end
-
-
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
-
Base.establish_connection(current_db_config)
-
-
check_pending!
-
end
-
-
3
def maintain_test_schema! #:nodoc:
-
if ActiveRecord::Base.maintain_test_schema
-
suppress_messages { load_schema_if_pending! }
-
end
-
end
-
-
3
def method_missing(name, *args, &block) #:nodoc:
-
158
nearest_delegate.send(name, *args, &block)
-
end
-
3
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
-
-
3
def migrate(direction)
-
20
new.migrate direction
-
end
-
-
# Disable the transaction wrapping this migration.
-
# You can still create your own transactions even after calling #disable_ddl_transaction!
-
#
-
# For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration].
-
3
def disable_ddl_transaction!
-
3
@disable_ddl_transaction = true
-
end
-
end
-
-
3
def disable_ddl_transaction #:nodoc:
-
394
self.class.disable_ddl_transaction
-
end
-
-
3
cattr_accessor :verbose
-
3
attr_accessor :name, :version
-
-
3
def initialize(name = self.class.name, version = nil)
-
763
@name = name
-
763
@version = version
-
763
@connection = nil
-
end
-
-
3
self.verbose = true
-
# instantiate the delegate object after initialize is defined
-
3
self.delegate = new
-
-
# Reverses the migration commands for the given block and
-
# the given migrations.
-
#
-
# The following migration will remove the table 'horses'
-
# and create the table 'apples' on the way up, and the reverse
-
# on the way down.
-
#
-
# class FixTLMigration < ActiveRecord::Migration[6.0]
-
# def change
-
# revert do
-
# create_table(:horses) do |t|
-
# t.text :content
-
# t.datetime :remind_at
-
# end
-
# end
-
# create_table(:apples) do |t|
-
# t.string :variety
-
# end
-
# end
-
# end
-
#
-
# Or equivalently, if +TenderloveMigration+ is defined as in the
-
# documentation for Migration:
-
#
-
# require_relative "20121212123456_tenderlove_migration"
-
#
-
# class FixupTLMigration < ActiveRecord::Migration[6.0]
-
# def change
-
# revert TenderloveMigration
-
#
-
# create_table(:apples) do |t|
-
# t.string :variety
-
# end
-
# end
-
# end
-
#
-
# This command can be nested.
-
3
def revert(*migration_classes)
-
243
run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
-
243
if block_given?
-
225
if connection.respond_to? :revert
-
54
connection.revert { yield }
-
else
-
198
recorder = command_recorder
-
198
@connection = recorder
-
198
suppress_messages do
-
396
connection.revert { yield }
-
end
-
195
@connection = recorder.delegate
-
195
recorder.replay(self)
-
end
-
end
-
end
-
-
3
def reverting?
-
39
connection.respond_to?(:reverting) && connection.reverting
-
end
-
-
3
ReversibleBlockHelper = Struct.new(:reverting) do #:nodoc:
-
3
def up
-
6
yield unless reverting
-
end
-
-
3
def down
-
6
yield if reverting
-
end
-
end
-
-
# Used to specify an operation that can be run in one direction or another.
-
# Call the methods +up+ and +down+ of the yielded object to run a block
-
# only in one given direction.
-
# The whole block will be called in the right order within the migration.
-
#
-
# In the following example, the looping on users will always be done
-
# when the three columns 'first_name', 'last_name' and 'full_name' exist,
-
# even when migrating down:
-
#
-
# class SplitNameMigration < ActiveRecord::Migration[6.0]
-
# def change
-
# add_column :users, :first_name, :string
-
# add_column :users, :last_name, :string
-
#
-
# reversible do |dir|
-
# User.reset_column_information
-
# User.all.each do |u|
-
# dir.up { u.first_name, u.last_name = u.full_name.split(' ') }
-
# dir.down { u.full_name = "#{u.first_name} #{u.last_name}" }
-
# u.save
-
# end
-
# end
-
#
-
# revert { add_column :users, :full_name, :string }
-
# end
-
# end
-
3
def reversible
-
6
helper = ReversibleBlockHelper.new(reverting?)
-
12
execute_block { yield helper }
-
end
-
-
# Used to specify an operation that is only run when migrating up
-
# (for example, populating a new column with its initial values).
-
#
-
# In the following example, the new column +published+ will be given
-
# the value +true+ for all existing records.
-
#
-
# class AddPublishedToPosts < ActiveRecord::Migration[6.0]
-
# def change
-
# add_column :posts, :published, :boolean, default: false
-
# up_only do
-
# execute "update posts set published = 'true'"
-
# end
-
# end
-
# end
-
3
def up_only
-
9
execute_block { yield } unless reverting?
-
end
-
-
# Runs the given migration classes.
-
# Last argument can specify options:
-
# - :direction (default is :up)
-
# - :revert (default is false)
-
3
def run(*migration_classes)
-
27
opts = migration_classes.extract_options!
-
27
dir = opts[:direction] || :up
-
27
dir = (dir == :down ? :up : :down) if opts[:revert]
-
27
if reverting?
-
# If in revert and going :up, say, we want to execute :down without reverting, so
-
18
revert { run(*migration_classes, direction: dir, revert: true) }
-
else
-
18
migration_classes.each do |migration_class|
-
18
migration_class.new.exec_migration(connection, dir)
-
end
-
end
-
end
-
-
3
def up
-
66
self.class.delegate = self
-
66
return unless self.class.respond_to?(:up)
-
62
self.class.up
-
end
-
-
3
def down
-
21
self.class.delegate = self
-
21
return unless self.class.respond_to?(:down)
-
18
self.class.down
-
end
-
-
# Execute this migration in the named direction
-
3
def migrate(direction)
-
715
return unless respond_to?(direction)
-
-
715
case direction
-
432
when :up then announce "migrating"
-
283
when :down then announce "reverting"
-
end
-
-
715
time = nil
-
715
ActiveRecord::Base.connection_pool.with_connection do |conn|
-
715
time = Benchmark.measure do
-
715
exec_migration(conn, direction)
-
end
-
end
-
-
712
case direction
-
432
when :up then announce "migrated (%.4fs)" % time.real; write
-
280
when :down then announce "reverted (%.4fs)" % time.real; write
-
end
-
end
-
-
3
def exec_migration(conn, direction)
-
733
@connection = conn
-
733
if respond_to?(:change)
-
403
if direction == :down
-
380
revert { change }
-
else
-
213
change
-
end
-
else
-
330
send(direction)
-
end
-
ensure
-
733
@connection = nil
-
end
-
-
3
def write(text = "")
-
6427
puts(text) if verbose
-
end
-
-
3
def announce(message)
-
1427
text = "#{version} #{name}: #{message}"
-
1427
length = [0, 75 - text.length].max
-
1427
write "== %s %s" % [text, "=" * length]
-
end
-
-
# Takes a message argument and outputs it as is.
-
# A second boolean argument can be passed to specify whether to indent or not.
-
3
def say(message, subitem = false)
-
5194
write "#{subitem ? " ->" : "--"} #{message}"
-
end
-
-
# Outputs text along with how long it took to run its block.
-
# If the block returns an integer it assumes it is the number of rows affected.
-
3
def say_with_time(message)
-
2600
say(message)
-
2600
result = nil
-
5200
time = Benchmark.measure { result = yield }
-
2587
say "%.4fs" % time.real, :subitem
-
2587
say("#{result} rows", :subitem) if result.is_a?(Integer)
-
2587
result
-
end
-
-
# Takes a block as an argument and suppresses any output generated by the block.
-
3
def suppress_messages
-
202
save, self.verbose = verbose, false
-
202
yield
-
ensure
-
202
self.verbose = save
-
end
-
-
3
def connection
-
8940
@connection || ActiveRecord::Base.connection
-
end
-
-
3
def method_missing(method, *arguments, &block)
-
2600
arg_list = arguments.map(&:inspect) * ", "
-
-
2600
say_with_time "#{method}(#{arg_list})" do
-
2600
unless connection.respond_to? :revert
-
2279
unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
-
2201
arguments[0] = proper_table_name(arguments.first, table_name_options)
-
2201
if [:rename_table, :add_foreign_key].include?(method) ||
-
(method == :remove_foreign_key && !arguments.second.is_a?(Hash))
-
65
arguments[1] = proper_table_name(arguments.second, table_name_options)
-
end
-
end
-
end
-
2600
return super unless connection.respond_to?(method)
-
2600
connection.send(method, *arguments, &block)
-
end
-
end
-
3
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
-
-
3
def copy(destination, sources, options = {})
-
51
copied = []
-
51
schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration
-
-
51
FileUtils.mkdir_p(destination) unless File.exist?(destination)
-
-
51
destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations
-
51
last = destination_migrations.last
-
51
sources.each do |scope, path|
-
66
source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).migrations
-
-
66
source_migrations.each do |migration|
-
123
source = File.binread(migration.filename)
-
123
inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
-
123
magic_comments = +""
-
123
loop do
-
# If we have a magic comment in the original migration,
-
# insert our comment after the first newline(end of the magic comment line)
-
# so the magic keep working.
-
# Note that magic comments must be at the first line(except sh-bang).
-
source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment|
-
129
magic_comments << magic_comment; ""
-
252
end || break
-
end
-
123
source = "#{magic_comments}#{inserted_comment}#{source}"
-
-
615
if duplicate = destination_migrations.detect { |m| m.name == migration.name }
-
54
if options[:on_skip] && duplicate.scope != scope.to_s
-
3
options[:on_skip].call(scope, migration)
-
end
-
54
next
-
end
-
-
69
migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
-
69
new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
-
69
old_path, migration.filename = migration.filename, new_path
-
69
last = migration
-
-
69
File.binwrite(migration.filename, source)
-
69
copied << migration
-
69
options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
-
69
destination_migrations << migration
-
end
-
end
-
-
51
copied
-
end
-
-
# Finds the correct table name given an Active Record object.
-
# Uses the Active Record object's own table_name, or pre/suffix from the
-
# options passed in.
-
3
def proper_table_name(name, options = {})
-
2287
if name.respond_to? :table_name
-
9
name.table_name
-
else
-
2278
"#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}"
-
end
-
end
-
-
# Determines the version number of the next migration.
-
3
def next_migration_number(number)
-
69
if ActiveRecord::Base.timestamped_migrations
-
48
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
-
else
-
21
SchemaMigration.normalize_migration_number(number)
-
end
-
end
-
-
# Builds a hash for use in ActiveRecord::Migration#proper_table_name using
-
# the Active Record object's table_name prefix and suffix
-
3
def table_name_options(config = ActiveRecord::Base) #:nodoc:
-
2272
{
-
table_name_prefix: config.table_name_prefix,
-
table_name_suffix: config.table_name_suffix
-
}
-
end
-
-
3
private
-
3
def execute_block
-
12
if connection.respond_to? :execute_block
-
3
super # use normal delegation to record the block
-
else
-
9
yield
-
end
-
end
-
-
3
def command_recorder
-
198
CommandRecorder.new(connection)
-
end
-
end
-
-
# MigrationProxy is used to defer loading of the actual migration classes
-
# until they are needed
-
3
MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
-
3
def initialize(name, version, filename, scope)
-
621
super
-
621
@migration = nil
-
end
-
-
3
def basename
-
File.basename(filename)
-
end
-
-
3
delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
-
-
3
private
-
3
def migration
-
126
@migration ||= load_migration
-
end
-
-
3
def load_migration
-
63
require(File.expand_path(filename))
-
63
name.constantize.new(name, version)
-
end
-
end
-
-
3
class MigrationContext #:nodoc:
-
3
attr_reader :migrations_paths, :schema_migration
-
-
3
def initialize(migrations_paths, schema_migration)
-
849
@migrations_paths = migrations_paths
-
849
@schema_migration = schema_migration
-
end
-
-
3
def migrate(target_version = nil, &block)
-
case
-
when target_version.nil?
-
31
up(target_version, &block)
-
when current_version == 0 && target_version == 0
-
[]
-
when current_version > target_version
-
3
down(target_version, &block)
-
else
-
22
up(target_version, &block)
-
56
end
-
end
-
-
3
def rollback(steps = 1)
-
39
move(:down, steps)
-
end
-
-
3
def forward(steps = 1)
-
18
move(:up, steps)
-
end
-
-
3
def up(target_version = nil)
-
98
selected_migrations = if block_given?
-
39
migrations.select { |m| yield m }
-
else
-
86
migrations
-
end
-
-
98
Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
-
end
-
-
3
def down(target_version = nil)
-
48
selected_migrations = if block_given?
-
12
migrations.select { |m| yield m }
-
else
-
45
migrations
-
end
-
-
48
Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
-
end
-
-
3
def run(direction, target_version)
-
3
Migrator.new(direction, migrations, schema_migration, target_version).run
-
end
-
-
3
def open
-
Migrator.new(:up, migrations, schema_migration)
-
end
-
-
3
def get_all_versions
-
511
if schema_migration.table_exists?
-
406
schema_migration.all_versions.map(&:to_i)
-
else
-
105
[]
-
end
-
end
-
-
3
def current_version
-
442
get_all_versions.max || 0
-
rescue ActiveRecord::NoDatabaseError
-
end
-
-
3
def needs_migration?
-
21
(migrations.collect(&:version) - get_all_versions).size > 0
-
end
-
-
3
def any_migrations?
-
6
migrations.any?
-
end
-
-
3
def migrations
-
257
migrations = migration_files.map do |file|
-
621
version, name, scope = parse_migration_filename(file)
-
621
raise IllegalMigrationNameError.new(file) unless version
-
621
version = version.to_i
-
621
name = name.camelize
-
-
621
MigrationProxy.new(name, version, file, scope)
-
end
-
-
257
migrations.sort_by(&:version)
-
end
-
-
3
def migrations_status
-
19
db_list = schema_migration.normalized_versions
-
-
19
file_list = migration_files.map do |file|
-
60
version, name, scope = parse_migration_filename(file)
-
60
raise IllegalMigrationNameError.new(file) unless version
-
60
version = schema_migration.normalize_migration_number(version)
-
60
status = db_list.delete(version) ? "up" : "down"
-
60
[status, version, (name + scope).humanize]
-
end.compact
-
-
19
db_list.map! do |version|
-
15
["up", version, "********** NO FILE **********"]
-
end
-
-
94
(db_list + file_list).sort_by { |_, version, _| version }
-
end
-
-
3
def current_environment
-
278
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
-
end
-
-
3
def protected_environment?
-
12
ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
-
end
-
-
3
def last_stored_environment
-
39
return nil unless ActiveRecord::InternalMetadata.enabled?
-
39
return nil if current_version == 0
-
39
raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
-
-
36
environment = ActiveRecord::InternalMetadata[:environment]
-
36
raise NoEnvironmentInSchemaError unless environment
-
36
environment
-
end
-
-
3
private
-
3
def migration_files
-
276
paths = Array(migrations_paths)
-
558
Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
-
end
-
-
3
def parse_migration_filename(filename)
-
681
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
-
end
-
-
3
def move(direction, steps)
-
57
migrator = Migrator.new(direction, migrations, schema_migration)
-
-
57
if current_version != 0 && !migrator.current_migration
-
raise UnknownMigrationVersionError.new(current_version)
-
end
-
-
57
start_index =
-
57
if current_version == 0
-
3
0
-
else
-
54
migrator.migrations.index(migrator.current_migration)
-
end
-
-
57
finish = migrator.migrations[start_index + steps]
-
57
version = finish ? finish.version : 0
-
57
send(direction, version)
-
end
-
end
-
-
3
class Migrator # :nodoc:
-
3
class << self
-
3
attr_accessor :migrations_paths
-
-
# For cases where a table doesn't exist like loading from schema cache
-
3
def current_version
-
MigrationContext.new(migrations_paths, SchemaMigration).current_version
-
end
-
end
-
-
3
self.migrations_paths = ["db/migrate"]
-
-
3
def initialize(direction, migrations, schema_migration, target_version = nil)
-
392
@direction = direction
-
392
@target_version = target_version
-
392
@migrated_versions = nil
-
392
@migrations = migrations
-
392
@schema_migration = schema_migration
-
-
392
validate(@migrations)
-
-
386
@schema_migration.create_table
-
386
ActiveRecord::InternalMetadata.create_table
-
end
-
-
3
def current_version
-
366
migrated.max || 0
-
end
-
-
3
def current_migration
-
528
migrations.detect { |m| m.version == current_version }
-
end
-
3
alias :current :current_migration
-
-
3
def run
-
25
if use_advisory_lock?
-
17
with_advisory_lock { run_without_lock }
-
else
-
16
run_without_lock
-
end
-
end
-
-
3
def migrate
-
297
if use_advisory_lock?
-
205
with_advisory_lock { migrate_without_lock }
-
else
-
194
migrate_without_lock
-
end
-
end
-
-
3
def runnable
-
290
runnable = migrations[start..finish]
-
290
if up?
-
587
runnable.reject { |m| ran?(m) }
-
else
-
# skip the last migration if we're headed down, but not ALL the way down
-
72
runnable.pop if target
-
171
runnable.find_all { |m| ran?(m) }
-
end
-
end
-
-
3
def migrations
-
1608
down? ? @migrations.reverse : @migrations.sort_by(&:version)
-
end
-
-
3
def pending_migrations
-
9
already_migrated = migrated
-
27
migrations.reject { |m| already_migrated.include?(m.version) }
-
end
-
-
3
def migrated
-
1595
@migrated_versions || load_migrated
-
end
-
-
3
def load_migrated
-
370
@migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
-
end
-
-
3
private
-
# Used for running a specific migration.
-
3
def run_without_lock
-
69
migration = migrations.detect { |m| m.version == @target_version }
-
24
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
-
15
result = execute_migration_in_transaction(migration)
-
-
12
record_environment
-
12
result
-
end
-
-
# Used for running multiple migrations up to or down to a certain value.
-
3
def migrate_without_lock
-
296
if invalid_target?
-
6
raise UnknownMigrationVersionError.new(@target_version)
-
end
-
-
290
result = runnable.each(&method(:execute_migration_in_transaction))
-
274
record_environment
-
274
result
-
end
-
-
# Stores the current environment in the database.
-
3
def record_environment
-
286
return if down?
-
208
ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
-
end
-
-
3
def ran?(migration)
-
468
migrated.include?(migration.version.to_i)
-
end
-
-
# Return true if a valid version is not provided.
-
3
def invalid_target?
-
296
@target_version && @target_version != 0 && !target
-
end
-
-
3
def execute_migration_in_transaction(migration)
-
387
return if down? && !migrated.include?(migration.version.to_i)
-
384
return if up? && migrated.include?(migration.version.to_i)
-
-
384
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
-
-
384
ddl_transaction(migration) do
-
384
migration.migrate(@direction)
-
365
record_version_state_after_migrating(migration.version)
-
end
-
rescue => e
-
19
msg = +"An error has occurred, "
-
19
msg << "this and " if use_transaction?(migration)
-
19
msg << "all later migrations canceled:\n\n#{e}"
-
19
raise StandardError, msg, e.backtrace
-
end
-
-
3
def target
-
1460
migrations.detect { |m| m.version == @target_version }
-
end
-
-
3
def finish
-
290
migrations.index(target) || migrations.size - 1
-
end
-
-
3
def start
-
290
up? ? 0 : (migrations.index(current) || 0)
-
end
-
-
3
def validate(migrations)
-
876
name, = migrations.group_by(&:name).find { |_, v| v.length > 1 }
-
392
raise DuplicateMigrationNameError.new(name) if name
-
-
1290
version, = migrations.group_by(&:version).find { |_, v| v.length > 1 }
-
389
raise DuplicateMigrationVersionError.new(version) if version
-
end
-
-
3
def record_version_state_after_migrating(version)
-
365
if down?
-
87
migrated.delete(version)
-
87
@schema_migration.delete_by(version: version.to_s)
-
else
-
278
migrated << version
-
278
@schema_migration.create!(version: version.to_s)
-
end
-
end
-
-
3
def up?
-
964
@direction == :up
-
end
-
-
3
def down?
-
2646
@direction == :down
-
end
-
-
# Wrap the migration in a transaction only if supported by the adapter.
-
3
def ddl_transaction(migration)
-
384
if use_transaction?(migration)
-
762
Base.transaction { yield }
-
else
-
3
yield
-
end
-
end
-
-
3
def use_transaction?(migration)
-
403
!migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
-
end
-
-
3
def use_advisory_lock?
-
322
Base.connection.advisory_locks_enabled?
-
end
-
-
3
def with_advisory_lock
-
114
lock_id = generate_migrator_advisory_lock_id
-
-
114
with_advisory_lock_connection do |connection|
-
114
got_lock = connection.get_advisory_lock(lock_id)
-
114
raise ConcurrentMigrationError unless got_lock
-
112
load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
-
112
yield
-
ensure
-
114
if got_lock && !connection.release_advisory_lock(lock_id)
-
1
raise ConcurrentMigrationError.new(
-
ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
-
)
-
end
-
end
-
end
-
-
3
def with_advisory_lock_connection
-
113
pool = ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
-
ActiveRecord::Base.connection_db_config
-
)
-
-
226
pool.with_connection { |connection| yield(connection) }
-
end
-
-
3
MIGRATOR_SALT = 2053462845
-
3
def generate_migrator_advisory_lock_id
-
119
db_name_hash = Zlib.crc32(Base.connection.current_database)
-
119
MIGRATOR_SALT * db_name_hash
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class Migration
-
# <tt>ActiveRecord::Migration::CommandRecorder</tt> records commands done during
-
# a migration and knows how to reverse those commands. The CommandRecorder
-
# knows how to invert the following commands:
-
#
-
# * add_column
-
# * add_foreign_key
-
# * add_check_constraint
-
# * add_index
-
# * add_reference
-
# * add_timestamps
-
# * change_column
-
# * change_column_default (must supply a :from and :to option)
-
# * change_column_null
-
# * change_column_comment (must supply a :from and :to option)
-
# * change_table_comment (must supply a :from and :to option)
-
# * create_join_table
-
# * create_table
-
# * disable_extension
-
# * drop_join_table
-
# * drop_table (must supply a block)
-
# * enable_extension
-
# * remove_column (must supply a type)
-
# * remove_columns (must specify at least one column name or more)
-
# * remove_foreign_key (must supply a second table)
-
# * remove_check_constraint
-
# * remove_index
-
# * remove_reference
-
# * remove_timestamps
-
# * rename_column
-
# * rename_index
-
# * rename_table
-
3
class CommandRecorder
-
3
ReversibleAndIrreversibleMethods = [
-
:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
-
:rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
-
:change_column_default, :add_reference, :remove_reference, :transaction,
-
:drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension,
-
:change_column, :execute, :remove_columns, :change_column_null,
-
:add_foreign_key, :remove_foreign_key,
-
:change_column_comment, :change_table_comment,
-
:add_check_constraint, :remove_check_constraint
-
]
-
3
include JoinTable
-
-
3
attr_accessor :commands, :delegate, :reverting
-
-
3
def initialize(delegate = nil)
-
419
@commands = []
-
419
@delegate = delegate
-
419
@reverting = false
-
end
-
-
# While executing the given block, the recorded will be in reverting mode.
-
# All commands recorded will end up being recorded reverted
-
# and in reverse order.
-
# For example:
-
#
-
# recorder.revert{ recorder.record(:rename_table, [:old, :new]) }
-
# # same effect as recorder.record(:rename_table, [:new, :old])
-
3
def revert
-
267
@reverting = !@reverting
-
267
previous = @commands
-
267
@commands = []
-
267
yield
-
ensure
-
267
@commands = previous.concat(@commands.reverse)
-
267
@reverting = !@reverting
-
end
-
-
# Record +command+. +command+ should be a method name and arguments.
-
# For example:
-
#
-
# recorder.record(:method_name, [:arg1, :arg2])
-
3
def record(*command, &block)
-
436
if @reverting
-
363
@commands << inverse_of(*command, &block)
-
else
-
73
@commands << (command << block)
-
end
-
end
-
-
# Returns the inverse of the given command. For example:
-
#
-
# recorder.inverse_of(:rename_table, [:old, :new])
-
# # => [:rename_table, [:new, :old]]
-
#
-
# If the inverse of a command requires several commands, returns array of commands.
-
#
-
# recorder.inverse_of(:remove_columns, [:some_table, :foo, :bar, type: :string])
-
# # => [[:add_column, :some_table, :foo, :string], [:add_column, :some_table, :bar, :string]]
-
#
-
# This method will raise an +IrreversibleMigration+ exception if it cannot
-
# invert the +command+.
-
3
def inverse_of(command, args, &block)
-
525
method = :"invert_#{command}"
-
525
raise IrreversibleMigration, <<~MSG unless respond_to?(method, true)
-
This migration uses #{command}, which is not automatically reversible.
-
To make the migration reversible you can either:
-
1. Define #up and #down methods in place of the #change method.
-
2. Use the #reversible method to define reversible behavior.
-
MSG
-
513
send(method, args, &block)
-
end
-
-
3
ReversibleAndIrreversibleMethods.each do |method|
-
90
class_eval <<-EOV, __FILE__, __LINE__ + 1
-
def #{method}(*args, &block) # def create_table(*args, &block)
-
record(:"#{method}", args, &block) # record(:create_table, args, &block)
-
end # end
-
EOV
-
90
ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
-
end
-
3
alias :add_belongs_to :add_reference
-
3
alias :remove_belongs_to :remove_reference
-
-
3
def change_table(table_name, **options) # :nodoc:
-
22
yield delegate.update_table_definition(table_name, self)
-
end
-
-
3
def replay(migration)
-
198
commands.each do |cmd, args, block|
-
321
migration.send(cmd, *args, &block)
-
end
-
end
-
-
3
private
-
3
module StraightReversions # :nodoc:
-
3
private
-
{
-
execute_block: :execute_block,
-
create_table: :drop_table,
-
create_join_table: :drop_join_table,
-
add_column: :remove_column,
-
add_index: :remove_index,
-
add_timestamps: :remove_timestamps,
-
add_reference: :remove_reference,
-
add_foreign_key: :remove_foreign_key,
-
add_check_constraint: :remove_check_constraint,
-
enable_extension: :disable_extension
-
3
}.each do |cmd, inv|
-
30
[[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
-
57
class_eval <<-EOV, __FILE__, __LINE__ + 1
-
def invert_#{method}(args, &block) # def invert_create_table(args, &block)
-
[:#{inverse}, args, block] # [:drop_table, args, block]
-
end # end
-
EOV
-
end
-
end
-
end
-
-
3
include StraightReversions
-
-
3
def invert_transaction(args)
-
6
sub_recorder = CommandRecorder.new(delegate)
-
12
sub_recorder.revert { yield }
-
-
3
invertions_proc = proc {
-
3
sub_recorder.replay(self)
-
}
-
-
3
[:transaction, args, invertions_proc]
-
end
-
-
3
def invert_drop_table(args, &block)
-
6
if args.size == 1 && block == nil
-
3
raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
-
end
-
3
super
-
end
-
-
3
def invert_rename_table(args)
-
3
[:rename_table, args.reverse]
-
end
-
-
3
def invert_remove_column(args)
-
9
raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
-
3
super
-
end
-
-
3
def invert_remove_columns(args)
-
6
unless args[-1].is_a?(Hash) && args[-1].has_key?(:type)
-
3
raise ActiveRecord::IrreversibleMigration, "remove_columns is only reversible if given a type."
-
end
-
-
3
[:add_columns, args]
-
end
-
-
3
def invert_rename_index(args)
-
3
table_name, old_name, new_name = args
-
3
[:rename_index, [table_name, new_name, old_name]]
-
end
-
-
3
def invert_rename_column(args)
-
6
table_name, old_name, new_name = args
-
6
[:rename_column, [table_name, new_name, old_name]]
-
end
-
-
3
def invert_remove_index(args)
-
24
options = args.extract_options!
-
24
table, columns = args
-
-
24
columns ||= options.delete(:column)
-
-
24
unless columns
-
3
raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
-
end
-
-
21
options.delete(:if_exists)
-
-
21
args = [table, columns]
-
21
args << options unless options.empty?
-
-
21
[:add_index, args]
-
end
-
-
3
alias :invert_add_belongs_to :invert_add_reference
-
3
alias :invert_remove_belongs_to :invert_remove_reference
-
-
3
def invert_change_column_default(args)
-
12
table, column, options = args
-
-
12
unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
-
3
raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option."
-
end
-
-
9
[:change_column_default, [table, column, from: options[:to], to: options[:from]]]
-
end
-
-
3
def invert_change_column_null(args)
-
6
args[2] = !args[2]
-
6
[:change_column_null, args]
-
end
-
-
3
def invert_remove_foreign_key(args)
-
39
options = args.extract_options!
-
39
from_table, to_table = args
-
-
39
to_table ||= options.delete(:to_table)
-
-
39
raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil?
-
-
30
reversed_args = [from_table, to_table]
-
30
reversed_args << options unless options.empty?
-
-
30
[:add_foreign_key, reversed_args]
-
end
-
-
3
def invert_change_column_comment(args)
-
5
table, column, options = args
-
-
5
unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
-
2
raise ActiveRecord::IrreversibleMigration, "change_column_comment is only reversible if given a :from and :to option."
-
end
-
-
3
[:change_column_comment, [table, column, from: options[:to], to: options[:from]]]
-
end
-
-
3
def invert_change_table_comment(args)
-
3
table, options = args
-
-
3
unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
-
raise ActiveRecord::IrreversibleMigration, "change_table_comment is only reversible if given a :from and :to option."
-
end
-
-
3
[:change_table_comment, [table, from: options[:to], to: options[:from]]]
-
end
-
-
3
def invert_remove_check_constraint(args)
-
6
raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2
-
3
super
-
end
-
-
3
def respond_to_missing?(method, _)
-
15
super || delegate.respond_to?(method)
-
end
-
-
# Forwards any missing method call to the \target.
-
3
def method_missing(method, *args, &block)
-
102
if delegate.respond_to?(method)
-
99
delegate.public_send(method, *args, &block)
-
else
-
3
super
-
end
-
end
-
3
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class Migration
-
3
module Compatibility # :nodoc: all
-
3
def self.find(version)
-
84
version = version.to_s
-
84
name = "V#{version.tr('.', '_')}"
-
84
unless const_defined?(name)
-
21
versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete("V").tr("_", ".").inspect }
-
3
raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
-
end
-
81
const_get(name)
-
end
-
-
3
V6_1 = Current
-
-
3
class V6_0 < V6_1
-
end
-
-
3
class V5_2 < V6_0
-
3
module TableDefinition
-
3
def timestamps(**options)
-
14
options[:precision] ||= nil
-
14
super
-
end
-
end
-
-
3
module CommandRecorder
-
3
def invert_transaction(args, &block)
-
3
[:transaction, args, block]
-
end
-
-
3
def invert_change_column_comment(args)
-
1
[:change_column_comment, args]
-
end
-
-
3
def invert_change_table_comment(args)
-
1
[:change_table_comment, args]
-
end
-
end
-
-
3
def create_table(table_name, **options)
-
75
if block_given?
-
114
super { |t| yield compatible_table_definition(t) }
-
else
-
2
super
-
end
-
end
-
-
3
def change_table(table_name, **options)
-
20
if block_given?
-
40
super { |t| yield compatible_table_definition(t) }
-
else
-
super
-
end
-
end
-
-
3
def create_join_table(table_1, table_2, **options)
-
24
if block_given?
-
36
super { |t| yield compatible_table_definition(t) }
-
else
-
super
-
end
-
end
-
-
3
def add_timestamps(table_name, **options)
-
6
options[:precision] ||= nil
-
6
super
-
end
-
-
3
private
-
3
def compatible_table_definition(t)
-
73
class << t
-
73
prepend TableDefinition
-
end
-
73
t
-
end
-
-
3
def command_recorder
-
49
recorder = super
-
49
class << recorder
-
49
prepend CommandRecorder
-
end
-
49
recorder
-
end
-
end
-
-
3
class V5_1 < V5_2
-
3
def change_column(table_name, column_name, type, **options)
-
1
if connection.adapter_name == "PostgreSQL"
-
1
super(table_name, column_name, type, **options.except(:default, :null, :comment))
-
1
connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
-
1
connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
-
1
connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
-
else
-
super
-
end
-
end
-
-
3
def create_table(table_name, **options)
-
72
if connection.adapter_name == "Mysql2"
-
super(table_name, options: "ENGINE=InnoDB", **options)
-
else
-
72
super
-
end
-
end
-
end
-
-
3
class V5_0 < V5_1
-
3
module TableDefinition
-
3
def primary_key(name, type = :primary_key, **options)
-
18
type = :integer if type == :primary_key
-
18
super
-
end
-
-
3
def references(*args, **options)
-
12
super(*args, type: :integer, **options)
-
end
-
3
alias :belongs_to :references
-
end
-
-
3
def create_table(table_name, **options)
-
72
if connection.adapter_name == "PostgreSQL"
-
28
if options[:id] == :uuid && !options.key?(:default)
-
1
options[:default] = "uuid_generate_v4()"
-
end
-
end
-
-
72
unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
-
72
if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
-
16
options[:default] = nil
-
end
-
end
-
-
# Since 5.1 PostgreSQL adapter uses bigserial type for primary
-
# keys by default and MySQL uses bigint. This compat layer makes old migrations utilize
-
# serial/int type instead -- the way it used to work before 5.1.
-
72
unless options.key?(:id)
-
18
options[:id] = :integer
-
end
-
-
72
super
-
end
-
-
3
def create_join_table(table_1, table_2, column_options: {}, **options)
-
24
column_options.reverse_merge!(type: :integer)
-
24
super
-
end
-
-
3
def add_column(table_name, column_name, type, **options)
-
12
if type == :primary_key
-
12
type = :integer
-
12
options[:primary_key] = true
-
end
-
12
super
-
end
-
-
3
def add_reference(table_name, ref_name, **options)
-
super(table_name, ref_name, type: :integer, **options)
-
end
-
3
alias :add_belongs_to :add_reference
-
-
3
private
-
3
def compatible_table_definition(t)
-
66
class << t
-
66
prepend TableDefinition
-
end
-
66
super
-
end
-
end
-
-
3
class V4_2 < V5_0
-
3
module TableDefinition
-
3
def references(*, **options)
-
9
options[:index] ||= false
-
9
super
-
end
-
3
alias :belongs_to :references
-
-
3
def timestamps(**options)
-
7
options[:null] = true if options[:null].nil?
-
7
super
-
end
-
end
-
-
3
def add_reference(table_name, ref_name, **options)
-
options[:index] ||= false
-
super
-
end
-
3
alias :add_belongs_to :add_reference
-
-
3
def add_timestamps(table_name, **options)
-
3
options[:null] = true if options[:null].nil?
-
3
super
-
end
-
-
3
def index_exists?(table_name, column_name, **options)
-
column_names = Array(column_name).map(&:to_s)
-
options[:name] =
-
if options[:name].present?
-
options[:name].to_s
-
else
-
connection.index_name(table_name, column: column_names)
-
end
-
super
-
end
-
-
3
def remove_index(table_name, column_name = nil, **options)
-
6
options[:name] = index_name_for_remove(table_name, column_name, options)
-
3
super
-
end
-
-
3
private
-
3
def compatible_table_definition(t)
-
38
class << t
-
38
prepend TableDefinition
-
end
-
38
super
-
end
-
-
3
def index_name_for_remove(table_name, column_name, options)
-
6
index_name = connection.index_name(table_name, column_name || options)
-
-
6
unless connection.index_name_exists?(table_name, index_name)
-
3
if options.key?(:name)
-
options_without_column = options.except(:column)
-
index_name_without_column = connection.index_name(table_name, options_without_column)
-
-
if connection.index_name_exists?(table_name, index_name_without_column)
-
return index_name_without_column
-
end
-
end
-
-
3
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
-
end
-
-
3
index_name
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class Migration
-
3
module JoinTable #:nodoc:
-
3
private
-
3
def find_join_table_name(table_1, table_2, options = {})
-
100
options.delete(:table_name) || join_table_name(table_1, table_2)
-
end
-
-
3
def join_table_name(table_1, table_2)
-
82
ModelSchema.derive_join_table_name(table_1, table_2).to_sym
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "monitor"
-
-
3
module ActiveRecord
-
3
module ModelSchema
-
3
extend ActiveSupport::Concern
-
-
##
-
# :singleton-method: primary_key_prefix_type
-
# :call-seq: primary_key_prefix_type
-
#
-
# The prefix type that will be prepended to every primary key column name.
-
# The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
-
# the Product class will look for "productid" instead of "id" as the primary column. If the
-
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
-
# that this is a global setting for all Active Records.
-
-
##
-
# :singleton-method: primary_key_prefix_type=
-
# :call-seq: primary_key_prefix_type=(prefix_type)
-
#
-
# Sets the prefix type that will be prepended to every primary key column name.
-
# The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
-
# the Product class will look for "productid" instead of "id" as the primary column. If the
-
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
-
# that this is a global setting for all Active Records.
-
-
##
-
# :singleton-method: table_name_prefix
-
# :call-seq: table_name_prefix
-
#
-
# The prefix string to prepend to every table name.
-
-
##
-
# :singleton-method: table_name_prefix=
-
# :call-seq: table_name_prefix=(prefix)
-
#
-
# Sets the prefix string to prepend to every table name. So if set to "basecamp_", all table
-
# names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient
-
# way of creating a namespace for tables in a shared database. By default, the prefix is the
-
# empty string.
-
#
-
# If you are organising your models within modules you can add a prefix to the models within
-
# a namespace by defining a singleton method in the parent module called table_name_prefix which
-
# returns your chosen prefix.
-
-
##
-
# :singleton-method: table_name_suffix
-
# :call-seq: table_name_suffix
-
#
-
# The suffix string to append to every table name.
-
-
##
-
# :singleton-method: table_name_suffix=
-
# :call-seq: table_name_suffix=(suffix)
-
#
-
# Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
-
# "people_basecamp"). By default, the suffix is the empty string.
-
#
-
# If you are organising your models within modules, you can add a suffix to the models within
-
# a namespace by defining a singleton method in the parent module called table_name_suffix which
-
# returns your chosen suffix.
-
-
##
-
# :singleton-method: schema_migrations_table_name
-
# :call-seq: schema_migrations_table_name
-
#
-
# The name of the schema migrations table. By default, the value is <tt>"schema_migrations"</tt>.
-
-
##
-
# :singleton-method: schema_migrations_table_name=
-
# :call-seq: schema_migrations_table_name=(table_name)
-
#
-
# Sets the name of the schema migrations table.
-
-
##
-
# :singleton-method: internal_metadata_table_name
-
# :call-seq: internal_metadata_table_name
-
#
-
# The name of the internal metadata table. By default, the value is <tt>"ar_internal_metadata"</tt>.
-
-
##
-
# :singleton-method: internal_metadata_table_name=
-
# :call-seq: internal_metadata_table_name=(table_name)
-
#
-
# Sets the name of the internal metadata table.
-
-
##
-
# :singleton-method: pluralize_table_names
-
# :call-seq: pluralize_table_names
-
#
-
# Indicates whether table names should be the pluralized versions of the corresponding class names.
-
# If true, the default table name for a Product class will be "products". If false, it would just be "product".
-
# See table_name for the full rules on table/class naming. This is true, by default.
-
-
##
-
# :singleton-method: pluralize_table_names=
-
# :call-seq: pluralize_table_names=(value)
-
#
-
# Set whether table names should be the pluralized versions of the corresponding class names.
-
# If true, the default table name for a Product class will be "products". If false, it would just be "product".
-
# See table_name for the full rules on table/class naming. This is true, by default.
-
-
##
-
# :singleton-method: implicit_order_column
-
# :call-seq: implicit_order_column
-
#
-
# The name of the column records are ordered by if no explicit order clause
-
# is used during an ordered finder call. If not set the primary key is used.
-
-
##
-
# :singleton-method: implicit_order_column=
-
# :call-seq: implicit_order_column=(column_name)
-
#
-
# Sets the column to sort records by when no explicit order clause is used
-
# during an ordered finder call. Useful when the primary key is not an
-
# auto-incrementing integer, for example when it's a UUID. Records are subsorted
-
# by the primary key if it exists to ensure deterministic results.
-
-
##
-
# :singleton-method: immutable_strings_by_default=
-
# :call-seq: immutable_strings_by_default=(bool)
-
#
-
# Determines whether columns should infer their type as `:string` or
-
# `:immutable_string`. This setting does not affect the behavior of
-
# `attribute :foo, :string`. Defaults to false.
-
-
3
included do
-
3
mattr_accessor :primary_key_prefix_type, instance_writer: false
-
-
3
class_attribute :table_name_prefix, instance_writer: false, default: ""
-
3
class_attribute :table_name_suffix, instance_writer: false, default: ""
-
3
class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
-
3
class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
-
3
class_attribute :pluralize_table_names, instance_writer: false, default: true
-
3
class_attribute :implicit_order_column, instance_accessor: false
-
3
class_attribute :immutable_strings_by_default, instance_accessor: false
-
-
3
self.protected_environments = ["production"]
-
3
self.inheritance_column = "type"
-
3
self.ignored_columns = [].freeze
-
-
3
delegate :type_for_attribute, :column_for_attribute, to: :class
-
-
3
initialize_load_schema_monitor
-
end
-
-
# Derives the join table name for +first_table+ and +second_table+. The
-
# table names appear in alphabetical order. A common prefix is removed
-
# (useful for namespaced models like Music::Artist and Music::Record):
-
#
-
# artists, records => artists_records
-
# records, artists => artists_records
-
# music_artists, music_records => music_artists_records
-
3
def self.derive_join_table_name(first_table, second_table) # :nodoc:
-
100
[first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
-
end
-
-
3
module ClassMethods
-
# Guesses the table name (in forced lower-case) based on the name of the class in the
-
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
-
# looks like: Reply < Message < ActiveRecord::Base, then Message is used
-
# to guess the table name even when called on Reply. The rules used to do the guess
-
# are handled by the Inflector class in Active Support, which knows almost all common
-
# English inflections. You can add new inflections in config/initializers/inflections.rb.
-
#
-
# Nested classes are given table names prefixed by the singular form of
-
# the parent's table name. Enclosing modules are not considered.
-
#
-
# ==== Examples
-
#
-
# class Invoice < ActiveRecord::Base
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice invoices
-
#
-
# class Invoice < ActiveRecord::Base
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice::Lineitem invoice_lineitems
-
#
-
# module Invoice
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice/lineitem.rb Invoice::Lineitem lineitems
-
#
-
# Additionally, the class-level +table_name_prefix+ is prepended and the
-
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
-
# the table name guess for an Invoice class becomes "myapp_invoices".
-
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
-
#
-
# You can also set your own table name explicitly:
-
#
-
# class Mouse < ActiveRecord::Base
-
# self.table_name = "mice"
-
# end
-
3
def table_name
-
41780
reset_table_name unless defined?(@table_name)
-
41780
@table_name
-
end
-
-
# Sets the table name explicitly. Example:
-
#
-
# class Project < ActiveRecord::Base
-
# self.table_name = "project"
-
# end
-
3
def table_name=(value)
-
3380
value = value && value.to_s
-
-
3380
if defined?(@table_name)
-
932
return if value == @table_name
-
270
reset_column_information if connected?
-
end
-
-
2718
@table_name = value
-
2718
@quoted_table_name = nil
-
2718
@arel_table = nil
-
2718
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
-
2718
@predicate_builder = nil
-
end
-
-
# Returns a quoted version of the table name, used to construct SQL statements.
-
3
def quoted_table_name
-
390
@quoted_table_name ||= connection.quote_table_name(table_name)
-
end
-
-
# Computes the table name, (re)sets it internally, and returns it.
-
3
def reset_table_name #:nodoc:
-
2234
self.table_name = if abstract_class?
-
27
superclass == Base ? nil : superclass.table_name
-
2207
elsif superclass.abstract_class?
-
39
superclass.table_name || compute_table_name
-
else
-
2168
compute_table_name
-
end
-
end
-
-
3
def full_table_name_prefix #:nodoc:
-
4780
(module_parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
-
end
-
-
3
def full_table_name_suffix #:nodoc:
-
4792
(module_parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
-
end
-
-
# The array of names of environments where destructive actions should be prohibited. By default,
-
# the value is <tt>["production"]</tt>.
-
3
def protected_environments
-
27
if defined?(@protected_environments)
-
27
@protected_environments
-
else
-
superclass.protected_environments
-
end
-
end
-
-
# Sets an array of names of environments where destructive actions should be prohibited.
-
3
def protected_environments=(environments)
-
21
@protected_environments = environments.map(&:to_s)
-
end
-
-
# Defines the name of the table column which will store the class name on single-table
-
# inheritance situations.
-
#
-
# The default inheritance column name is +type+, which means it's a
-
# reserved word inside Active Record. To be able to use single-table
-
# inheritance with another column name, or to use the column +type+ in
-
# your own model for something else, you can set +inheritance_column+:
-
#
-
# self.inheritance_column = 'zoink'
-
3
def inheritance_column
-
367107
(@inheritance_column ||= nil) || superclass.inheritance_column
-
end
-
-
# Sets the value of inheritance_column
-
3
def inheritance_column=(value)
-
48
@inheritance_column = value.to_s
-
48
@explicit_inheritance_column = true
-
end
-
-
# The list of columns names the model should ignore. Ignored columns won't have attribute
-
# accessors defined, and won't be referenced in SQL queries.
-
3
def ignored_columns
-
74751
if defined?(@ignored_columns)
-
36032
@ignored_columns
-
else
-
38719
superclass.ignored_columns
-
end
-
end
-
-
# Sets the columns names the model should ignore. Ignored columns won't have attribute
-
# accessors defined, and won't be referenced in SQL queries.
-
3
def ignored_columns=(columns)
-
15
reload_schema_from_cache
-
15
@ignored_columns = columns.map(&:to_s).freeze
-
end
-
-
3
def sequence_name
-
18
if base_class?
-
12
@sequence_name ||= reset_sequence_name
-
else
-
6
(@sequence_name ||= nil) || base_class.sequence_name
-
end
-
end
-
-
3
def reset_sequence_name #:nodoc:
-
204
@explicit_sequence_name = false
-
204
@sequence_name = connection.default_sequence_name(table_name, primary_key)
-
end
-
-
# Sets the name of the sequence to use when generating ids to the given
-
# value, or (if the value is +nil+ or +false+) to the value returned by the
-
# given block. This is required for Oracle and is useful for any
-
# database which relies on sequences for primary key generation.
-
#
-
# If a sequence name is not explicitly set when using Oracle,
-
# it will default to the commonly used pattern of: #{table_name}_seq
-
#
-
# If a sequence name is not explicitly set when using PostgreSQL, it
-
# will discover the sequence corresponding to your primary key for you.
-
#
-
# class Project < ActiveRecord::Base
-
# self.sequence_name = "projectseq" # default would have been "project_seq"
-
# end
-
3
def sequence_name=(value)
-
6
@sequence_name = value.to_s
-
6
@explicit_sequence_name = true
-
end
-
-
# Determines if the primary key values should be selected from their
-
# corresponding sequence before the insert statement.
-
3
def prefetch_primary_key?
-
11099
connection.prefetch_primary_key?(table_name)
-
end
-
-
# Returns the next value that will be used as the primary key on
-
# an insert statement.
-
3
def next_sequence_value
-
connection.next_sequence_value(sequence_name)
-
end
-
-
# Indicates whether the table associated with this class exists
-
3
def table_exists?
-
6154
connection.schema_cache.data_source_exists?(table_name)
-
end
-
-
3
def attributes_builder # :nodoc:
-
246035
unless defined?(@attributes_builder) && @attributes_builder
-
1574
defaults = _default_attributes.except(*(column_names - [primary_key]))
-
1574
@attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
-
end
-
246035
@attributes_builder
-
end
-
-
3
def columns_hash # :nodoc:
-
37475
load_schema
-
37469
@columns_hash
-
end
-
-
3
def columns
-
8582
load_schema
-
8582
@columns ||= columns_hash.values.freeze
-
end
-
-
3
def attribute_types # :nodoc:
-
265094
load_schema
-
265083
@attribute_types ||= Hash.new(Type.default_value)
-
end
-
-
3
def yaml_encoder # :nodoc:
-
145
@yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
-
end
-
-
# Returns the type of the attribute with the given name, after applying
-
# all modifiers. This method is the only valid source of information for
-
# anything related to the types of a model's attributes. This method will
-
# access the database and load the model's schema if it is required.
-
#
-
# The return value of this method will implement the interface described
-
# by ActiveModel::Type::Value (though the object itself may not subclass
-
# it).
-
#
-
# +attr_name+ The name of the attribute to retrieve the type for. Must be
-
# a string or a symbol.
-
3
def type_for_attribute(attr_name, &block)
-
188654
attr_name = attr_name.to_s
-
188654
attr_name = attribute_aliases[attr_name] || attr_name
-
-
188654
if block
-
440
attribute_types.fetch(attr_name, &block)
-
else
-
188214
attribute_types[attr_name]
-
end
-
end
-
-
# Returns the column object for the named attribute.
-
# Returns an +ActiveRecord::ConnectionAdapters::NullColumn+ if the
-
# named attribute does not exist.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# person = Person.new
-
# person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
-
# # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
-
#
-
# person.column_for_attribute(:nothing)
-
# # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
-
3
def column_for_attribute(name)
-
45
name = name.to_s
-
45
columns_hash.fetch(name) do
-
6
ConnectionAdapters::NullColumn.new(name)
-
end
-
end
-
-
# Returns a hash where the keys are column names and the values are
-
# default values when instantiating the Active Record object for this table.
-
3
def column_defaults
-
3147
load_schema
-
3147
@column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
-
end
-
-
3
def _default_attributes # :nodoc:
-
49300
load_schema
-
49300
@default_attributes ||= ActiveModel::AttributeSet.new({})
-
end
-
-
# Returns an array of column names as strings.
-
3
def column_names
-
26919
@column_names ||= columns.map(&:name).freeze
-
end
-
-
3
def symbol_column_to_string(name_symbol) # :nodoc:
-
24662
@symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym)
-
24662
@symbol_column_to_string_name_hash[name_symbol]
-
end
-
-
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
-
# and columns used for single table inheritance have been removed.
-
3
def content_columns
-
3
@content_columns ||= columns.reject do |c|
-
54
c.name == primary_key ||
-
c.name == inheritance_column ||
-
c.name.end_with?("_id", "_count")
-
end.freeze
-
end
-
-
# Resets all the cached information about columns, which will cause them
-
# to be reloaded on the next request.
-
#
-
# The most common usage pattern for this method is probably in a migration,
-
# when just after creating a table you want to populate it with some default
-
# values, eg:
-
#
-
# class CreateJobLevels < ActiveRecord::Migration[6.0]
-
# def up
-
# create_table :job_levels do |t|
-
# t.integer :id
-
# t.string :name
-
#
-
# t.timestamps
-
# end
-
#
-
# JobLevel.reset_column_information
-
# %w{assistant executive manager director}.each do |type|
-
# JobLevel.create(name: type)
-
# end
-
# end
-
#
-
# def down
-
# drop_table :job_levels
-
# end
-
# end
-
3
def reset_column_information
-
2006
connection.clear_cache!
-
2006
([self] + descendants).each(&:undefine_attribute_methods)
-
2006
connection.schema_cache.clear_data_source_cache!(table_name)
-
-
2006
reload_schema_from_cache
-
2006
initialize_find_by_cache
-
end
-
-
3
protected
-
3
def initialize_load_schema_monitor
-
2943
@load_schema_monitor = Monitor.new
-
end
-
-
3
private
-
3
def inherited(child_class)
-
2940
super
-
2940
child_class.initialize_load_schema_monitor
-
end
-
-
3
def schema_loaded?
-
363598
defined?(@schema_loaded) && @schema_loaded
-
end
-
-
3
def load_schema
-
363598
return if schema_loaded?
-
65838
@load_schema_monitor.synchronize do
-
65838
return if defined?(@columns_hash) && @columns_hash
-
-
3471
load_schema!
-
-
3454
@schema_loaded = true
-
rescue
-
17
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
-
17
raise
-
end
-
end
-
-
3
def load_schema!
-
3471
unless table_name
-
3
raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
-
end
-
-
3468
columns_hash = connection.schema_cache.columns_hash(table_name)
-
3465
columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
-
3465
@columns_hash = columns_hash.freeze
-
3465
@columns_hash.each do |name, column|
-
28589
type = connection.lookup_cast_type_from_column(column)
-
28586
type = _convert_type_from_options(type)
-
28586
define_attribute(
-
name,
-
type,
-
default: column.default,
-
user_provided_default: false
-
)
-
end
-
end
-
-
3
def reload_schema_from_cache
-
4568
@arel_table = nil
-
4568
@column_names = nil
-
4568
@symbol_column_to_string_name_hash = nil
-
4568
@attribute_types = nil
-
4568
@content_columns = nil
-
4568
@default_attributes = nil
-
4568
@column_defaults = nil
-
4568
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
-
4568
@attributes_builder = nil
-
4568
@columns = nil
-
4568
@columns_hash = nil
-
4568
@schema_loaded = false
-
4568
@attribute_names = nil
-
4568
@yaml_encoder = nil
-
4568
direct_descendants.each do |descendant|
-
1946
descendant.send(:reload_schema_from_cache)
-
end
-
end
-
-
# Guesses the table name, but does not decorate it with prefix and suffix information.
-
3
def undecorated_table_name(class_name = base_class.name)
-
1606
table_name = class_name.to_s.demodulize.underscore
-
1606
pluralize_table_names ? table_name.pluralize : table_name
-
end
-
-
# Computes and returns a table name according to default conventions.
-
3
def compute_table_name
-
2195
if base_class?
-
# Nested classes are prefixed with singular parent table name.
-
1606
if module_parent < Base && !module_parent.abstract_class?
-
58
contained = module_parent.table_name
-
58
contained = contained.singularize if module_parent.pluralize_table_names
-
58
contained += "_"
-
end
-
-
1606
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
-
else
-
# STI subclasses always use their superclass' table.
-
589
base_class.table_name
-
end
-
end
-
-
3
def _convert_type_from_options(type)
-
28586
if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
-
27
type.to_immutable_string
-
else
-
28559
type
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/hash/except"
-
3
require "active_support/core_ext/module/redefine_method"
-
3
require "active_support/core_ext/hash/indifferent_access"
-
-
3
module ActiveRecord
-
3
module NestedAttributes #:nodoc:
-
3
class TooManyRecords < ActiveRecordError
-
end
-
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
3
class_attribute :nested_attributes_options, instance_writer: false, default: {}
-
end
-
-
# = Active Record Nested Attributes
-
#
-
# Nested attributes allow you to save attributes on associated records
-
# through the parent. By default nested attribute updating is turned off
-
# and you can enable it using the accepts_nested_attributes_for class
-
# method. When you enable nested attributes an attribute writer is
-
# defined on the model.
-
#
-
# The attribute writer is named after the association, which means that
-
# in the following example, two new methods are added to your model:
-
#
-
# <tt>author_attributes=(attributes)</tt> and
-
# <tt>pages_attributes=(attributes)</tt>.
-
#
-
# class Book < ActiveRecord::Base
-
# has_one :author
-
# has_many :pages
-
#
-
# accepts_nested_attributes_for :author, :pages
-
# end
-
#
-
# Note that the <tt>:autosave</tt> option is automatically enabled on every
-
# association that accepts_nested_attributes_for is used for.
-
#
-
# === One-to-one
-
#
-
# Consider a Member model that has one Avatar:
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar
-
# end
-
#
-
# Enabling nested attributes on a one-to-one association allows you to
-
# create the member and avatar in one go:
-
#
-
# params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
-
# member = Member.create(params[:member])
-
# member.avatar.id # => 2
-
# member.avatar.icon # => 'smiling'
-
#
-
# It also allows you to update the avatar through the member:
-
#
-
# params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
-
# member.update params[:member]
-
# member.avatar.icon # => 'sad'
-
#
-
# If you want to update the current avatar without providing the id, you must add <tt>:update_only</tt> option.
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar, update_only: true
-
# end
-
#
-
# params = { member: { avatar_attributes: { icon: 'sad' } } }
-
# member.update params[:member]
-
# member.avatar.id # => 2
-
# member.avatar.icon # => 'sad'
-
#
-
# By default you will only be able to set and update attributes on the
-
# associated model. If you want to destroy the associated model through the
-
# attributes hash, you have to enable it first using the
-
# <tt>:allow_destroy</tt> option.
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar, allow_destroy: true
-
# end
-
#
-
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
-
# value that evaluates to +true+, you will destroy the associated model:
-
#
-
# member.avatar_attributes = { id: '2', _destroy: '1' }
-
# member.avatar.marked_for_destruction? # => true
-
# member.save
-
# member.reload.avatar # => nil
-
#
-
# Note that the model will _not_ be destroyed until the parent is saved.
-
#
-
# Also note that the model will not be destroyed unless you also specify
-
# its id in the updated hash.
-
#
-
# === One-to-many
-
#
-
# Consider a member that has a number of posts:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# You can now set or update attributes on the associated posts through
-
# an attribute hash for a member: include the key +:posts_attributes+
-
# with an array of hashes of post attributes as a value.
-
#
-
# For each hash that does _not_ have an <tt>id</tt> key a new record will
-
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
-
# that evaluates to +true+.
-
#
-
# params = { member: {
-
# name: 'joe', posts_attributes: [
-
# { title: 'Kari, the awesome Ruby documentation browser!' },
-
# { title: 'The egalitarian assumption of the modern citizen' },
-
# { title: '', _destroy: '1' } # this will be ignored
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# You may also set a +:reject_if+ proc to silently ignore any new record
-
# hashes if they fail to pass your criteria. For example, the previous
-
# example could be rewritten as:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
-
# end
-
#
-
# params = { member: {
-
# name: 'joe', posts_attributes: [
-
# { title: 'Kari, the awesome Ruby documentation browser!' },
-
# { title: 'The egalitarian assumption of the modern citizen' },
-
# { title: '' } # this will be ignored because of the :reject_if proc
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# Alternatively, +:reject_if+ also accepts a symbol for using methods:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: :new_record?
-
# end
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
-
#
-
# def reject_posts(attributes)
-
# attributes['title'].blank?
-
# end
-
# end
-
#
-
# If the hash contains an <tt>id</tt> key that matches an already
-
# associated record, the matching record will be modified:
-
#
-
# member.attributes = {
-
# name: 'Joe',
-
# posts_attributes: [
-
# { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
-
# { id: 2, title: '[UPDATED] other post' }
-
# ]
-
# }
-
#
-
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
-
# member.posts.second.title # => '[UPDATED] other post'
-
#
-
# However, the above applies if the parent model is being updated as well.
-
# For example, If you wanted to create a +member+ named _joe_ and wanted to
-
# update the +posts+ at the same time, that would give an
-
# ActiveRecord::RecordNotFound error.
-
#
-
# By default the associated records are protected from being destroyed. If
-
# you want to destroy any of the associated records through the attributes
-
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
-
# option. This will allow you to also use the <tt>_destroy</tt> key to
-
# destroy existing records:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, allow_destroy: true
-
# end
-
#
-
# params = { member: {
-
# posts_attributes: [{ id: '2', _destroy: '1' }]
-
# }}
-
#
-
# member.attributes = params[:member]
-
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
-
# member.posts.length # => 2
-
# member.save
-
# member.reload.posts.length # => 1
-
#
-
# Nested attributes for an associated collection can also be passed in
-
# the form of a hash of hashes instead of an array of hashes:
-
#
-
# Member.create(
-
# name: 'joe',
-
# posts_attributes: {
-
# first: { title: 'Foo' },
-
# second: { title: 'Bar' }
-
# }
-
# )
-
#
-
# has the same effect as
-
#
-
# Member.create(
-
# name: 'joe',
-
# posts_attributes: [
-
# { title: 'Foo' },
-
# { title: 'Bar' }
-
# ]
-
# )
-
#
-
# The keys of the hash which is the value for +:posts_attributes+ are
-
# ignored in this case.
-
# However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
-
# such keys, otherwise the hash will be wrapped in an array and
-
# interpreted as an attribute hash for a single post.
-
#
-
# Passing attributes for an associated collection in the form of a hash
-
# of hashes can be used with hashes generated from HTTP/HTML parameters,
-
# where there may be no natural way to submit an array of hashes.
-
#
-
# === Saving
-
#
-
# All changes to models, including the destruction of those marked for
-
# destruction, are saved and destroyed automatically and atomically when
-
# the parent model is saved. This happens inside the transaction initiated
-
# by the parent's save method. See ActiveRecord::AutosaveAssociation.
-
#
-
# === Validating the presence of a parent model
-
#
-
# If you want to validate that a child record is associated with a parent
-
# record, you can use the +validates_presence_of+ method and the +:inverse_of+
-
# key as this example illustrates:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts, inverse_of: :member
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :member, inverse_of: :posts
-
# validates_presence_of :member
-
# end
-
#
-
# Note that if you do not specify the +:inverse_of+ option, then
-
# Active Record will try to automatically guess the inverse association
-
# based on heuristics.
-
#
-
# For one-to-one nested associations, if you build the new (in-memory)
-
# child object yourself before assignment, then this module will not
-
# overwrite it, e.g.:
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar
-
#
-
# def avatar
-
# super || build_avatar(width: 200)
-
# end
-
# end
-
#
-
# member = Member.new
-
# member.avatar_attributes = {icon: 'sad'}
-
# member.avatar.width # => 200
-
3
module ClassMethods
-
30
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
-
-
# Defines an attributes writer for the specified association(s).
-
#
-
# Supported options:
-
# [:allow_destroy]
-
# If true, destroys any members from the attributes hash with a
-
# <tt>_destroy</tt> key and a value that evaluates to +true+
-
# (e.g. 1, '1', true, or 'true'). This option is off by default.
-
# [:reject_if]
-
# Allows you to specify a Proc or a Symbol pointing to a method
-
# that checks whether a record should be built for a certain attribute
-
# hash. The hash is passed to the supplied Proc or the method
-
# and it should return either +true+ or +false+. When no +:reject_if+
-
# is specified, a record will be built for all attribute hashes that
-
# do not have a <tt>_destroy</tt> value that evaluates to true.
-
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
-
# that will reject a record where all the attributes are blank excluding
-
# any value for +_destroy+.
-
# [:limit]
-
# Allows you to specify the maximum number of associated records that
-
# can be processed with the nested attributes. Limit also can be specified
-
# as a Proc or a Symbol pointing to a method that should return a number.
-
# If the size of the nested attributes array exceeds the specified limit,
-
# NestedAttributes::TooManyRecords exception is raised. If omitted, any
-
# number of associations can be processed.
-
# Note that the +:limit+ option is only applicable to one-to-many
-
# associations.
-
# [:update_only]
-
# For a one-to-one association, this option allows you to specify how
-
# nested attributes are going to be used when an associated record already
-
# exists. In general, an existing record may either be updated with the
-
# new set of attribute values or be replaced by a wholly new record
-
# containing those values. By default the +:update_only+ option is +false+
-
# and the nested attributes are used to update the existing record only
-
# if they include the record's <tt>:id</tt> value. Otherwise a new
-
# record will be instantiated and used to replace the existing one.
-
# However if the +:update_only+ option is +true+, the nested attributes
-
# are used to update the record's attributes always, regardless of
-
# whether the <tt>:id</tt> is present. The option is ignored for collection
-
# associations.
-
#
-
# Examples:
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, reject_if: :all_blank
-
# # creates avatar_attributes= and posts_attributes=
-
# accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
-
3
def accepts_nested_attributes_for(*attr_names)
-
294
options = { allow_destroy: false, update_only: false }
-
294
options.update(attr_names.extract_options!)
-
294
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
-
294
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
-
-
294
attr_names.each do |association_name|
-
321
if reflection = _reflect_on_association(association_name)
-
318
reflection.autosave = true
-
318
define_autosave_validation_callbacks(reflection)
-
-
318
nested_attributes_options = self.nested_attributes_options.dup
-
318
nested_attributes_options[association_name.to_sym] = options
-
318
self.nested_attributes_options = nested_attributes_options
-
-
318
type = (reflection.collection? ? :collection : :one_to_one)
-
318
generate_association_writer(association_name, type)
-
else
-
3
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
-
end
-
end
-
end
-
-
3
private
-
# Generates a writer method for this association. Serves as a point for
-
# accessing the objects in the association. For example, this method
-
# could generate the following:
-
#
-
# def pirate_attributes=(attributes)
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
-
# end
-
#
-
# This redirects the attempts to write objects in an association through
-
# the helper methods defined below. Makes it seem like the nested
-
# associations are just regular associations.
-
3
def generate_association_writer(association_name, type)
-
318
generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
-
silence_redefinition_of_method :#{association_name}_attributes=
-
def #{association_name}_attributes=(attributes)
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
-
end
-
eoruby
-
end
-
end
-
-
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
-
# used in conjunction with fields_for to build a form element for the
-
# destruction of this association.
-
#
-
# See ActionView::Helpers::FormHelper::fields_for for more info.
-
3
def _destroy
-
6
marked_for_destruction?
-
end
-
-
3
private
-
# Attribute hash keys that should not be assigned as normal attributes.
-
# These hash keys are nested attributes implementation details.
-
3
UNASSIGNABLE_KEYS = %w( id _destroy )
-
-
# Assigns the given attributes to the association.
-
#
-
# If an associated record does not yet exist, one will be instantiated. If
-
# an associated record already exists, the method's behavior depends on
-
# the value of the update_only option. If update_only is +false+ and the
-
# given attributes include an <tt>:id</tt> that matches the existing record's
-
# id, then the existing record will be modified. If no <tt>:id</tt> is provided
-
# it will be replaced with a new record. If update_only is +true+ the existing
-
# record will be modified regardless of whether an <tt>:id</tt> is provided.
-
#
-
# If the given attributes include a matching <tt>:id</tt> attribute, or
-
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
-
# then the existing record will be marked for destruction.
-
3
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
-
240
options = nested_attributes_options[association_name]
-
240
if attributes.respond_to?(:permitted?)
-
3
attributes = attributes.to_h
-
end
-
240
attributes = attributes.with_indifferent_access
-
240
existing_record = send(association_name)
-
-
240
if (options[:update_only] || !attributes["id"].blank?) && existing_record &&
-
(options[:update_only] || existing_record.id.to_s == attributes["id"].to_s)
-
135
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
-
-
105
elsif attributes["id"].present?
-
6
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
-
-
99
elsif !reject_new_record?(association_name, attributes)
-
72
assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
-
-
72
if existing_record && existing_record.new_record?
-
6
existing_record.assign_attributes(assignable_attributes)
-
6
association(association_name).initialize_attributes(existing_record)
-
else
-
66
method = :"build_#{association_name}"
-
66
if respond_to?(method)
-
63
send(method, assignable_attributes)
-
else
-
3
raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
-
end
-
end
-
end
-
end
-
-
# Assigns the given attributes to the collection association.
-
#
-
# Hashes with an <tt>:id</tt> value matching an existing associated record
-
# will update that record. Hashes without an <tt>:id</tt> value will build
-
# a new record for the association. Hashes with a matching <tt>:id</tt>
-
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
-
# matched record for destruction.
-
#
-
# For example:
-
#
-
# assign_nested_attributes_for_collection_association(:people, {
-
# '1' => { id: '1', name: 'Peter' },
-
# '2' => { name: 'John' },
-
# '3' => { id: '2', _destroy: true }
-
# })
-
#
-
# Will update the name of the Person with ID 1, build a new associated
-
# person with the name 'John', and mark the associated Person with ID 2
-
# for destruction.
-
#
-
# Also accepts an Array of attribute hashes:
-
#
-
# assign_nested_attributes_for_collection_association(:people, [
-
# { id: '1', name: 'Peter' },
-
# { name: 'John' },
-
# { id: '2', _destroy: true }
-
# ])
-
3
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
-
405
options = nested_attributes_options[association_name]
-
405
if attributes_collection.respond_to?(:permitted?)
-
3
attributes_collection = attributes_collection.to_h
-
end
-
-
405
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
-
6
raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
-
end
-
-
399
check_record_limit!(options[:limit], attributes_collection)
-
-
390
if attributes_collection.is_a? Hash
-
198
keys = attributes_collection.keys
-
198
attributes_collection = if keys.include?("id") || keys.include?(:id)
-
15
[attributes_collection]
-
else
-
183
attributes_collection.values
-
end
-
end
-
-
390
association = association(association_name)
-
-
390
existing_records = if association.loaded?
-
150
association.target
-
else
-
609
attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact
-
240
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
-
end
-
-
390
attributes_collection.each do |attributes|
-
627
if attributes.respond_to?(:permitted?)
-
6
attributes = attributes.to_h
-
end
-
627
attributes = attributes.with_indifferent_access
-
-
627
if attributes["id"].blank?
-
231
unless reject_new_record?(association_name, attributes)
-
213
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
-
end
-
978
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
-
384
unless call_reject_if(association_name, attributes)
-
# Make sure we are operating on the actual object which is in the association's
-
# proxy_target array (either by finding it, or adding it if not found)
-
# Take into account that the proxy_target may have changed due to callbacks
-
834
target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
-
381
if target_record
-
276
existing_record = target_record
-
else
-
105
association.add_to_target(existing_record, skip_callbacks: true)
-
end
-
-
381
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
-
end
-
else
-
12
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
-
end
-
end
-
end
-
-
# Takes in a limit and checks if the attributes_collection has too many
-
# records. It accepts limit in the form of symbol, proc, or
-
# number-like object (anything that can be compared with an integer).
-
#
-
# Raises TooManyRecords error if the attributes_collection is
-
# larger than the limit.
-
3
def check_record_limit!(limit, attributes_collection)
-
399
if limit
-
27
limit = \
-
case limit
-
when Symbol
-
9
send(limit)
-
when Proc
-
9
limit.call
-
else
-
9
limit
-
end
-
-
27
if limit && attributes_collection.size > limit
-
9
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
-
end
-
end
-
end
-
-
# Updates a record with the +attributes+ or marks it for destruction if
-
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
-
3
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
-
510
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
-
510
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
-
end
-
-
# Determines if a hash contains a truthy _destroy key.
-
3
def has_destroy_flag?(hash)
-
1380
Type::Boolean.new.cast(hash["_destroy"])
-
end
-
-
# Determines if a new record should be rejected by checking
-
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
-
# association and evaluates to +true+.
-
3
def reject_new_record?(association_name, attributes)
-
330
will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
-
end
-
-
# Determines if a record with the particular +attributes+ should be
-
# rejected by calling the reject_if Symbol or Proc (if defined).
-
# The reject_if option is defined by +accepts_nested_attributes_for+.
-
#
-
# Returns false if there is a +destroy_flag+ on the attributes.
-
3
def call_reject_if(association_name, attributes)
-
831
return false if will_be_destroyed?(association_name, attributes)
-
-
723
case callback = nested_attributes_options[association_name][:reject_if]
-
when Symbol
-
9
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
-
when Proc
-
495
callback.call(attributes)
-
end
-
end
-
-
# Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
-
3
def will_be_destroyed?(association_name, attributes)
-
1161
allow_destroy?(association_name) && has_destroy_flag?(attributes)
-
end
-
-
3
def allow_destroy?(association_name)
-
1161
nested_attributes_options[association_name][:allow_destroy]
-
end
-
-
3
def raise_nested_attributes_record_not_found!(association_name, record_id)
-
18
model = self.class._reflect_on_association(association_name).klass.name
-
18
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
-
model, "id", record_id)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record No Touching
-
3
module NoTouching
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
# Lets you selectively disable calls to +touch+ for the
-
# duration of a block.
-
#
-
# ==== Examples
-
# ActiveRecord::Base.no_touching do
-
# Project.first.touch # does nothing
-
# Message.first.touch # does nothing
-
# end
-
#
-
# Project.no_touching do
-
# Project.first.touch # does nothing
-
# Message.first.touch # works, but does not touch the associated project
-
# end
-
#
-
3
def no_touching(&block)
-
18
NoTouching.apply_to(self, &block)
-
end
-
end
-
-
3
class << self
-
3
def apply_to(klass) #:nodoc:
-
18
klasses.push(klass)
-
18
yield
-
ensure
-
18
klasses.pop
-
end
-
-
3
def applied_to?(klass) #:nodoc:
-
1038
klasses.any? { |k| k >= klass }
-
end
-
-
3
private
-
3
def klasses
-
1041
Thread.current[:no_touching_classes] ||= []
-
end
-
end
-
-
# Returns +true+ if the class has +no_touching+ set, +false+ otherwise.
-
#
-
# Project.no_touching do
-
# Project.first.no_touching? # true
-
# Message.first.no_touching? # false
-
# end
-
#
-
3
def no_touching?
-
1005
NoTouching.applied_to?(self.class)
-
end
-
-
3
def touch_later(*) # :nodoc:
-
429
super unless no_touching?
-
end
-
-
3
def touch(*, **) # :nodoc:
-
546
super unless no_touching?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module NullRelation # :nodoc:
-
3
def pluck(*column_names)
-
30
[]
-
end
-
-
3
def delete_all
-
3
0
-
end
-
-
3
def update_all(_updates)
-
3
0
-
end
-
-
3
def delete(_id_or_array)
-
3
0
-
end
-
-
3
def empty?
-
3
true
-
end
-
-
3
def none?
-
3
true
-
end
-
-
3
def any?
-
15
false
-
end
-
-
3
def one?
-
3
false
-
end
-
-
3
def many?
-
3
false
-
end
-
-
3
def to_sql
-
3
""
-
end
-
-
3
def calculate(operation, _column_name)
-
54
case operation
-
when :count, :sum
-
36
group_values.any? ? Hash.new : 0
-
when :average, :minimum, :maximum
-
18
group_values.any? ? Hash.new : nil
-
end
-
end
-
-
3
def exists?(_conditions = :none)
-
8
false
-
end
-
-
3
def or(other)
-
6
other.spawn
-
end
-
-
3
private
-
3
def exec_queries
-
30
@records = [].freeze
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/insert_all"
-
-
3
module ActiveRecord
-
# = Active Record \Persistence
-
3
module Persistence
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
-
# The resulting object is returned whether the object was saved successfully to the database or not.
-
#
-
# The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
-
# attributes on the objects that are to be created.
-
#
-
# ==== Examples
-
# # Create a single new object
-
# User.create(first_name: 'Jamie')
-
#
-
# # Create an Array of new objects
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
-
#
-
# # Create a single object and pass it into a block to set other attributes.
-
# User.create(first_name: 'Jamie') do |u|
-
# u.is_admin = false
-
# end
-
#
-
# # Creating an Array of new objects using a block, where the block is executed for each object:
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
-
# u.is_admin = false
-
# end
-
3
def create(attributes = nil, &block)
-
2872
if attributes.is_a?(Array)
-
75
attributes.collect { |attr| create(attr, &block) }
-
else
-
2851
object = new(attributes, &block)
-
2842
object.save
-
2824
object
-
end
-
end
-
-
# Creates an object (or multiple objects) and saves it to the database,
-
# if validations pass. Raises a RecordInvalid error if validations fail,
-
# unlike Base#create.
-
#
-
# The +attributes+ parameter can be either a Hash or an Array of Hashes.
-
# These describe which attributes to be created on the object, or
-
# multiple objects when given an Array of Hashes.
-
3
def create!(attributes = nil, &block)
-
4671
if attributes.is_a?(Array)
-
21
attributes.collect { |attr| create!(attr, &block) }
-
else
-
4662
object = new(attributes, &block)
-
4656
object.save!
-
4581
object
-
end
-
end
-
-
# Inserts a single record into the database in a single SQL INSERT
-
# statement. It does not instantiate any models nor does it trigger
-
# Active Record callbacks or validations. Though passed values
-
# go through Active Record's type casting and serialization.
-
#
-
# See <tt>ActiveRecord::Persistence#insert_all</tt> for documentation.
-
3
def insert(attributes, returning: nil, unique_by: nil)
-
12
insert_all([ attributes ], returning: returning, unique_by: unique_by)
-
end
-
-
# Inserts multiple records into the database in a single SQL INSERT
-
# statement. It does not instantiate any models nor does it trigger
-
# Active Record callbacks or validations. Though passed values
-
# go through Active Record's type casting and serialization.
-
#
-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
-
# the attributes for a single row and must have the same keys.
-
#
-
# Rows are considered to be unique by every unique index on the table. Any
-
# duplicate rows are skipped.
-
# Override with <tt>:unique_by</tt> (see below).
-
#
-
# Returns an <tt>ActiveRecord::Result</tt> with its contents based on
-
# <tt>:returning</tt> (see below).
-
#
-
# ==== Options
-
#
-
# [:returning]
-
# (PostgreSQL only) An array of attributes to return for all successfully
-
# inserted records, which by default is the primary key.
-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
-
# clause entirely.
-
#
-
# [:unique_by]
-
# (PostgreSQL and SQLite only) By default rows are considered to be unique
-
# by every unique index on the table. Any duplicate rows are skipped.
-
#
-
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
-
#
-
# Consider a Book model where no duplicate ISBNs make sense, but if any
-
# row has an existing id, or is not unique by another unique index,
-
# <tt>ActiveRecord::RecordNotUnique</tt> is raised.
-
#
-
# Unique indexes can be identified by columns or name:
-
#
-
# unique_by: :isbn
-
# unique_by: %i[ author_id name ]
-
# unique_by: :index_books_on_isbn
-
#
-
# Because it relies on the index information from the database
-
# <tt>:unique_by</tt> is recommended to be paired with
-
# Active Record's schema_cache.
-
#
-
# ==== Example
-
#
-
# # Insert records and skip inserting any duplicates.
-
# # Here "Eloquent Ruby" is skipped because its id is not unique.
-
#
-
# Book.insert_all([
-
# { id: 1, title: "Rework", author: "David" },
-
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
-
# ])
-
3
def insert_all(attributes, returning: nil, unique_by: nil)
-
66
InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
-
end
-
-
# Inserts a single record into the database in a single SQL INSERT
-
# statement. It does not instantiate any models nor does it trigger
-
# Active Record callbacks or validations. Though passed values
-
# go through Active Record's type casting and serialization.
-
#
-
# See <tt>ActiveRecord::Persistence#insert_all!</tt> for more.
-
3
def insert!(attributes, returning: nil)
-
6
insert_all!([ attributes ], returning: returning)
-
end
-
-
# Inserts multiple records into the database in a single SQL INSERT
-
# statement. It does not instantiate any models nor does it trigger
-
# Active Record callbacks or validations. Though passed values
-
# go through Active Record's type casting and serialization.
-
#
-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
-
# the attributes for a single row and must have the same keys.
-
#
-
# Raises <tt>ActiveRecord::RecordNotUnique</tt> if any rows violate a
-
# unique index on the table. In that case, no rows are inserted.
-
#
-
# To skip duplicate rows, see <tt>ActiveRecord::Persistence#insert_all</tt>.
-
# To replace them, see <tt>ActiveRecord::Persistence#upsert_all</tt>.
-
#
-
# Returns an <tt>ActiveRecord::Result</tt> with its contents based on
-
# <tt>:returning</tt> (see below).
-
#
-
# ==== Options
-
#
-
# [:returning]
-
# (PostgreSQL only) An array of attributes to return for all successfully
-
# inserted records, which by default is the primary key.
-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
-
# clause entirely.
-
#
-
# ==== Examples
-
#
-
# # Insert multiple records
-
# Book.insert_all!([
-
# { title: "Rework", author: "David" },
-
# { title: "Eloquent Ruby", author: "Russ" }
-
# ])
-
#
-
# # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
-
# # does not have a unique id.
-
# Book.insert_all!([
-
# { id: 1, title: "Rework", author: "David" },
-
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
-
# ])
-
3
def insert_all!(attributes, returning: nil)
-
40
InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute
-
end
-
-
# Updates or inserts (upserts) a single record into the database in a
-
# single SQL INSERT statement. It does not instantiate any models nor does
-
# it trigger Active Record callbacks or validations. Though passed values
-
# go through Active Record's type casting and serialization.
-
#
-
# See <tt>ActiveRecord::Persistence#upsert_all</tt> for documentation.
-
3
def upsert(attributes, returning: nil, unique_by: nil)
-
9
upsert_all([ attributes ], returning: returning, unique_by: unique_by)
-
end
-
-
# Updates or inserts (upserts) multiple records into the database in a
-
# single SQL INSERT statement. It does not instantiate any models nor does
-
# it trigger Active Record callbacks or validations. Though passed values
-
# go through Active Record's type casting and serialization.
-
#
-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
-
# the attributes for a single row and must have the same keys.
-
#
-
# Returns an <tt>ActiveRecord::Result</tt> with its contents based on
-
# <tt>:returning</tt> (see below).
-
#
-
# ==== Options
-
#
-
# [:returning]
-
# (PostgreSQL only) An array of attributes to return for all successfully
-
# inserted records, which by default is the primary key.
-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
-
# clause entirely.
-
#
-
# [:unique_by]
-
# (PostgreSQL and SQLite only) By default rows are considered to be unique
-
# by every unique index on the table. Any duplicate rows are skipped.
-
#
-
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
-
#
-
# Consider a Book model where no duplicate ISBNs make sense, but if any
-
# row has an existing id, or is not unique by another unique index,
-
# <tt>ActiveRecord::RecordNotUnique</tt> is raised.
-
#
-
# Unique indexes can be identified by columns or name:
-
#
-
# unique_by: :isbn
-
# unique_by: %i[ author_id name ]
-
# unique_by: :index_books_on_isbn
-
#
-
# Because it relies on the index information from the database
-
# <tt>:unique_by</tt> is recommended to be paired with
-
# Active Record's schema_cache.
-
#
-
# ==== Examples
-
#
-
# # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
-
# # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
-
#
-
# Book.upsert_all([
-
# { title: "Rework", author: "David", isbn: "1" },
-
# { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
-
# ], unique_by: :isbn)
-
#
-
# Book.find_by(isbn: "1").title # => "Eloquent Ruby"
-
3
def upsert_all(attributes, returning: nil, unique_by: nil)
-
76
InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute
-
end
-
-
# Given an attributes hash, +instantiate+ returns a new instance of
-
# the appropriate class. Accepts only keys as strings.
-
#
-
# For example, +Post.all+ may return Comments, Messages, and Emails
-
# by storing the record's subclass in a +type+ attribute. By calling
-
# +instantiate+ instead of +new+, finder methods ensure they get new
-
# instances of the appropriate class for each record.
-
#
-
# See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
-
# how this "single-table" inheritance mapping is implemented.
-
3
def instantiate(attributes, column_types = {}, &block)
-
27284
klass = discriminate_class_for_record(attributes)
-
27275
instantiate_instance_of(klass, attributes, column_types, &block)
-
end
-
-
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
-
# The resulting object is returned whether the object was saved successfully to the database or not.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - This should be the id or an array of ids to be updated.
-
# * +attributes+ - This should be a hash of attributes or an array of hashes.
-
#
-
# ==== Examples
-
#
-
# # Updates one record
-
# Person.update(15, user_name: "Samuel", group: "expert")
-
#
-
# # Updates multiple records
-
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
-
# Person.update(people.keys, people.values)
-
#
-
# # Updates multiple records from the result of a relation
-
# people = Person.where(group: "expert")
-
# people.update(group: "masters")
-
#
-
# Note: Updating a large number of records will run an UPDATE
-
# query for each record, which may cause a performance issue.
-
# When running callbacks is not needed for each record update,
-
# it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
-
# for updating all records in a single query.
-
3
def update(id = :all, attributes)
-
24
if id.is_a?(Array)
-
48
id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
-
21
object.update(attributes[idx])
-
}
-
9
elsif id == :all
-
18
all.each { |record| record.update(attributes) }
-
else
-
6
if ActiveRecord::Base === id
-
3
raise ArgumentError,
-
"You are passing an instance of ActiveRecord::Base to `update`. " \
-
"Please pass the id of the object by calling `.id`."
-
end
-
3
object = find(id)
-
object.update(attributes)
-
object
-
end
-
end
-
-
# Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
-
# therefore all callbacks and filters are fired off before the object is deleted. This method is
-
# less efficient than #delete but allows cleanup methods and other actions to be run.
-
#
-
# This essentially finds the object (or multiple objects) with the given id, creates a new object
-
# from the attributes, and then calls destroy on it.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - This should be the id or an array of ids to be destroyed.
-
#
-
# ==== Examples
-
#
-
# # Destroy a single object
-
# Todo.destroy(1)
-
#
-
# # Destroy multiple objects
-
# todos = [1,2,3]
-
# Todo.destroy(todos)
-
3
def destroy(id)
-
18
if id.is_a?(Array)
-
6
find(id).each(&:destroy)
-
else
-
12
find(id).destroy
-
end
-
end
-
-
# Deletes the row with a primary key matching the +id+ argument, using an
-
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
-
# Record objects are not instantiated, so the object's callbacks are not
-
# executed, including any <tt>:dependent</tt> association options.
-
#
-
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
-
#
-
# Note: Although it is often much faster than the alternative, #destroy,
-
# skipping callbacks might bypass business logic in your application
-
# that ensures referential integrity or performs other essential jobs.
-
#
-
# ==== Examples
-
#
-
# # Delete a single row
-
# Todo.delete(1)
-
#
-
# # Delete multiple rows
-
# Todo.delete([2,3,4])
-
3
def delete(id_or_array)
-
15
delete_by(primary_key => id_or_array)
-
end
-
-
3
def _insert_record(values) # :nodoc:
-
12437
primary_key = self.primary_key
-
12437
primary_key_value = nil
-
-
12437
if primary_key && Hash === values
-
11634
primary_key_value = values[primary_key]
-
-
11634
if !primary_key_value && prefetch_primary_key?
-
primary_key_value = next_sequence_value
-
values[primary_key] = primary_key_value
-
end
-
end
-
-
12437
if values.empty?
-
1294
im = arel_table.compile_insert(connection.empty_insert_statement_value(primary_key))
-
1294
im.into arel_table
-
else
-
11143
im = arel_table.compile_insert(_substitute_values(values))
-
end
-
-
12437
connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
-
end
-
-
3
def _update_record(values, constraints) # :nodoc:
-
5937
constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) }
-
-
2791
um = arel_table.where(
-
constraints.reduce(&:and)
-
).compile_update(_substitute_values(values), primary_key)
-
-
2791
connection.update(um, "#{self} Update")
-
end
-
-
3
def _delete_record(constraints) # :nodoc:
-
2506
constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) }
-
-
1217
dm = Arel::DeleteManager.new
-
1217
dm.from(arel_table)
-
1217
dm.wheres = constraints
-
-
1217
connection.delete(dm, "#{self} Destroy")
-
end
-
-
3
private
-
# Given a class, an attributes hash, +instantiate_instance_of+ returns a
-
# new instance of the class. Accepts only keys as strings.
-
3
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
-
246032
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
-
246032
klass.allocate.init_with_attributes(attributes, &block)
-
end
-
-
# Called by +instantiate+ to decide which class to use for a new
-
# record instance.
-
#
-
# See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
-
# the single-table inheritance discriminator.
-
3
def discriminate_class_for_record(record)
-
7772
self
-
end
-
-
3
def _substitute_values(values)
-
17942
values.map do |name, value|
-
39962
attr = arel_table[name]
-
39962
bind = predicate_builder.build_bind_attribute(attr.name, value)
-
39962
[attr, bind]
-
end
-
end
-
end
-
-
# Returns true if this object hasn't been saved yet -- that is, a record
-
# for the object doesn't exist in the database yet; otherwise, returns false.
-
3
def new_record?
-
83400
@new_record
-
end
-
-
# Returns true if this object was just created -- that is, prior to the last
-
# save, the object didn't exist in the database and new_record? would have
-
# returned true.
-
3
def previously_new_record?
-
19
@previously_new_record
-
end
-
-
# Returns true if this object has been destroyed, otherwise returns false.
-
3
def destroyed?
-
25981
@destroyed
-
end
-
-
# Returns true if the record is persisted, i.e. it's not a new record and it was
-
# not destroyed, otherwise returns false.
-
3
def persisted?
-
32797
!(@new_record || @destroyed)
-
end
-
-
##
-
# :call-seq:
-
# save(**options)
-
#
-
# Saves the model.
-
#
-
# If the model is new, a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# By default, save always runs validations. If any of them fail the action
-
# is cancelled and #save returns +false+, and the record won't be saved. However, if you supply
-
# <tt>validate: false</tt>, validations are bypassed altogether. See
-
# ActiveRecord::Validations for more information.
-
#
-
# By default, #save also sets the +updated_at+/+updated_on+ attributes to
-
# the current time. However, if you supply <tt>touch: false</tt>, these
-
# timestamps will not be updated.
-
#
-
# There's a series of callbacks associated with #save. If any of the
-
# <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled and
-
# #save returns +false+. See ActiveRecord::Callbacks for further
-
# details.
-
#
-
# Attributes marked as readonly are silently ignored if the record is
-
# being updated.
-
3
def save(**options, &block)
-
7870
create_or_update(**options, &block)
-
rescue ActiveRecord::RecordInvalid
-
31
false
-
end
-
-
##
-
# :call-seq:
-
# save!(**options)
-
#
-
# Saves the model.
-
#
-
# If the model is new, a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# By default, #save! always runs validations. If any of them fail
-
# ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply
-
# <tt>validate: false</tt>, validations are bypassed altogether. See
-
# ActiveRecord::Validations for more information.
-
#
-
# By default, #save! also sets the +updated_at+/+updated_on+ attributes to
-
# the current time. However, if you supply <tt>touch: false</tt>, these
-
# timestamps will not be updated.
-
#
-
# There's a series of callbacks associated with #save!. If any of
-
# the <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled
-
# and #save! raises ActiveRecord::RecordNotSaved. See
-
# ActiveRecord::Callbacks for further details.
-
#
-
# Attributes marked as readonly are silently ignored if the record is
-
# being updated.
-
#
-
# Unless an error is raised, returns true.
-
3
def save!(**options, &block)
-
8146
create_or_update(**options, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
-
end
-
-
# Deletes the record in the database and freezes this instance to
-
# reflect that no changes should be made (since they can't be
-
# persisted). Returns the frozen instance.
-
#
-
# The row is simply removed with an SQL +DELETE+ statement on the
-
# record's primary key, and no callbacks are executed.
-
#
-
# Note that this will also delete records marked as {#readonly?}[rdoc-ref:Core#readonly?].
-
#
-
# To enforce the object's +before_destroy+ and +after_destroy+
-
# callbacks or any <tt>:dependent</tt> association
-
# options, use #destroy.
-
3
def delete
-
280
_delete_row if persisted?
-
280
@destroyed = true
-
280
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with #destroy. If the
-
# <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
-
# and #destroy returns +false+.
-
# See ActiveRecord::Callbacks for further details.
-
3
def destroy
-
977
_raise_readonly_record_error if readonly?
-
974
destroy_associations
-
974
@_trigger_destroy_callback = if persisted?
-
958
destroy_row > 0
-
else
-
16
true
-
end
-
962
@destroyed = true
-
962
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with #destroy!. If the
-
# <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
-
# and #destroy! raises ActiveRecord::RecordNotDestroyed.
-
# See ActiveRecord::Callbacks for further details.
-
3
def destroy!
-
309
destroy || _raise_record_not_destroyed
-
end
-
-
# Returns an instance of the specified +klass+ with the attributes of the
-
# current record. This is mostly useful in relation to single-table
-
# inheritance structures where you want a subclass to appear as the
-
# superclass. This can be used along with record identification in
-
# Action Pack to allow, say, <tt>Client < Company</tt> to do something
-
# like render <tt>partial: @client.becomes(Company)</tt> to render that
-
# instance using the companies/company partial instead of clients/client.
-
#
-
# Note: The new instance will share a link to the same attributes as the original class.
-
# Therefore the sti column value will still be the same.
-
# Any change to the attributes on either instance will affect both instances.
-
# If you want to change the sti column as well, use #becomes! instead.
-
3
def becomes(klass)
-
105
became = klass.allocate
-
105
became.send(:initialize)
-
105
became.instance_variable_set(:@attributes, @attributes)
-
105
became.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
-
105
became.instance_variable_set(:@new_record, new_record?)
-
105
became.instance_variable_set(:@destroyed, destroyed?)
-
105
became.errors.copy!(errors)
-
105
became
-
end
-
-
# Wrapper around #becomes that also changes the instance's sti column value.
-
# This is especially useful if you want to persist the changed class in your
-
# database.
-
#
-
# Note: The old instance's sti column value will be changed too, as both objects
-
# share the same set of attributes.
-
3
def becomes!(klass)
-
18
became = becomes(klass)
-
18
sti_type = nil
-
18
if !klass.descends_from_active_record?
-
12
sti_type = klass.sti_name
-
end
-
18
became.public_send("#{klass.inheritance_column}=", sti_type)
-
18
became
-
end
-
-
# Updates a single attribute and saves the record.
-
# This is especially useful for boolean flags on existing records. Also note that
-
#
-
# * Validation is skipped.
-
# * \Callbacks are invoked.
-
# * updated_at/updated_on column is updated if that column is available.
-
# * Updates all the attributes that are dirty in this object.
-
#
-
# This method raises an ActiveRecord::ActiveRecordError if the
-
# attribute is marked as readonly.
-
#
-
# Also see #update_column.
-
3
def update_attribute(name, value)
-
57
name = name.to_s
-
57
verify_readonly_attribute(name)
-
54
public_send("#{name}=", value)
-
-
54
save(validate: false)
-
end
-
-
# Updates the attributes of the model from the passed-in hash and saves the
-
# record, all wrapped in a transaction. If the object is invalid, the saving
-
# will fail and false will be returned.
-
3
def update(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
409
with_transaction_returning_status do
-
409
assign_attributes(attributes)
-
403
save
-
end
-
end
-
-
3
alias update_attributes update
-
3
deprecate update_attributes: "please, use update instead"
-
-
# Updates its receiver just like #update but calls #save! instead
-
# of +save+, so an exception is raised if the record is invalid and saving will fail.
-
3
def update!(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
381
with_transaction_returning_status do
-
381
assign_attributes(attributes)
-
381
save!
-
end
-
end
-
-
3
alias update_attributes! update!
-
3
deprecate update_attributes!: "please, use update! instead"
-
-
# Equivalent to <code>update_columns(name => value)</code>.
-
3
def update_column(name, value)
-
83
update_columns(name => value)
-
end
-
-
# Updates the attributes directly in the database issuing an UPDATE SQL
-
# statement and sets them in the receiver:
-
#
-
# user.update_columns(last_request_at: Time.current)
-
#
-
# This is the fastest way to update attributes because it goes straight to
-
# the database, but take into account that in consequence the regular update
-
# procedures are totally bypassed. In particular:
-
#
-
# * \Validations are skipped.
-
# * \Callbacks are skipped.
-
# * +updated_at+/+updated_on+ are not updated.
-
# * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
-
#
-
# This method raises an ActiveRecord::ActiveRecordError when called on new
-
# objects, or when at least one of the attributes is marked as readonly.
-
3
def update_columns(attributes)
-
407
raise ActiveRecordError, "cannot update a new record" if new_record?
-
401
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
-
-
401
attributes = attributes.transform_keys do |key|
-
458
name = key.to_s
-
458
name = self.class.attribute_aliases[name] || name
-
458
verify_readonly_attribute(name) || name
-
end
-
-
395
id_in_database = self.id_in_database
-
395
attributes.each do |k, v|
-
449
write_attribute_without_type_cast(k, v)
-
end
-
-
392
affected_rows = self.class._update_record(
-
attributes,
-
@primary_key => id_in_database
-
)
-
-
392
affected_rows == 1
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
-
# The increment is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
3
def increment(attribute, by = 1)
-
637
self[attribute] ||= 0
-
637
self[attribute] += by
-
637
self
-
end
-
-
# Wrapper around #increment that writes the update to the database.
-
# Only +attribute+ is updated; the record itself is not saved.
-
# This means that any other modified attributes will still be dirty.
-
# Validations and callbacks are skipped. Supports the +touch+ option from
-
# +update_counters+, see that for more.
-
# Returns +self+.
-
3
def increment!(attribute, by = 1, touch: nil)
-
486
increment(attribute, by)
-
486
change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
-
486
self.class.update_counters(id, attribute => change, touch: touch)
-
486
public_send(:"clear_#{attribute}_change")
-
486
self
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
-
# The decrement is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
3
def decrement(attribute, by = 1)
-
6
increment(attribute, -by)
-
end
-
-
# Wrapper around #decrement that writes the update to the database.
-
# Only +attribute+ is updated; the record itself is not saved.
-
# This means that any other modified attributes will still be dirty.
-
# Validations and callbacks are skipped. Supports the +touch+ option from
-
# +update_counters+, see that for more.
-
# Returns +self+.
-
3
def decrement!(attribute, by = 1, touch: nil)
-
18
increment!(attribute, -by, touch: touch)
-
end
-
-
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
-
# if the predicate returns +true+ the attribute will become +false+. This
-
# method toggles directly the underlying value without calling any setter.
-
# Returns +self+.
-
#
-
# Example:
-
#
-
# user = User.first
-
# user.banned? # => false
-
# user.toggle(:banned)
-
# user.banned? # => true
-
#
-
3
def toggle(attribute)
-
6
self[attribute] = !public_send("#{attribute}?")
-
6
self
-
end
-
-
# Wrapper around #toggle that saves the record. This method differs from
-
# its non-bang version in the sense that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
3
def toggle!(attribute)
-
3
toggle(attribute).update_attribute(attribute, self[attribute])
-
end
-
-
# Reloads the record from the database.
-
#
-
# This method finds the record by its primary key (which could be assigned
-
# manually) and modifies the receiver in-place:
-
#
-
# account = Account.new
-
# # => #<Account id: nil, email: nil>
-
# account.id = 1
-
# account.reload
-
# # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
-
# # => #<Account id: 1, email: 'account@example.com'>
-
#
-
# Attributes are reloaded from the database, and caches busted, in
-
# particular the associations cache and the QueryCache.
-
#
-
# If the record no longer exists in the database ActiveRecord::RecordNotFound
-
# is raised. Otherwise, in addition to the in-place modification the method
-
# returns +self+ for convenience.
-
#
-
# The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
-
#
-
# reload(lock: true) # reload with pessimistic locking
-
#
-
# Reloading is commonly used in test suites to test something is actually
-
# written to the database, or when some action modifies the corresponding
-
# row in the database but not the object in memory:
-
#
-
# assert account.deposit!(25)
-
# assert_equal 25, account.credit # check it is updated in memory
-
# assert_equal 25, account.reload.credit # check it is also persisted
-
#
-
# Another common use case is optimistic locking handling:
-
#
-
# def with_optimistic_retry
-
# begin
-
# yield
-
# rescue ActiveRecord::StaleObjectError
-
# begin
-
# # Reload lock_version in particular.
-
# reload
-
# rescue ActiveRecord::RecordNotFound
-
# # If the record is gone there is nothing to do.
-
# else
-
# retry
-
# end
-
# end
-
# end
-
#
-
3
def reload(options = nil)
-
2233
self.class.connection.clear_query_cache
-
-
2233
fresh_object =
-
2233
if options && options[:lock]
-
46
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
-
else
-
4420
self.class.unscoped { self.class.find(id) }
-
end
-
-
2215
@attributes = fresh_object.instance_variable_get(:@attributes)
-
2215
@new_record = false
-
2215
@previously_new_record = false
-
2215
self
-
end
-
-
# Saves the record with the updated_at/on attributes set to the current time
-
# or the time specified.
-
# Please note that no validation is performed and only the +after_touch+,
-
# +after_commit+ and +after_rollback+ callbacks are executed.
-
#
-
# This method can be passed attribute names and an optional time argument.
-
# If attribute names are passed, they are updated along with updated_at/on
-
# attributes. If no time argument is passed, the current time is used as default.
-
#
-
# product.touch # updates updated_at/on with current time
-
# product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time
-
# product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
-
# product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
-
#
-
# If used along with {belongs_to}[rdoc-ref:Associations::ClassMethods#belongs_to]
-
# then +touch+ will invoke +touch+ method on associated object.
-
#
-
# class Brake < ActiveRecord::Base
-
# belongs_to :car, touch: true
-
# end
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :corporation, touch: true
-
# end
-
#
-
# # triggers @brake.car.touch and @brake.car.corporation.touch
-
# @brake.touch
-
#
-
# Note that +touch+ must be used on a persisted object, or else an
-
# ActiveRecordError will be thrown. For example:
-
#
-
# ball = Ball.new
-
# ball.touch(:updated_at) # => raises ActiveRecordError
-
#
-
3
def touch(*names, time: nil)
-
537
_raise_record_not_touched_error unless persisted?
-
-
534
attribute_names = timestamp_attributes_for_update_in_model
-
attribute_names |= names.map! do |name|
-
381
name = name.to_s
-
381
self.class.attribute_aliases[name] || name
-
534
end unless names.empty?
-
-
534
unless attribute_names.empty?
-
504
affected_rows = _touch_row(attribute_names, time)
-
489
@_trigger_update_callback = affected_rows == 1
-
else
-
30
true
-
end
-
end
-
-
3
private
-
# A hook to be overridden by association modules.
-
3
def destroy_associations
-
end
-
-
3
def destroy_row
-
886
_delete_row
-
end
-
-
3
def _delete_row
-
1145
self.class._delete_record(@primary_key => id_in_database)
-
end
-
-
3
def _touch_row(attribute_names, time)
-
504
time ||= current_time_from_proper_timezone
-
-
504
attribute_names.each do |attr_name|
-
567
_write_attribute(attr_name, time)
-
end
-
-
504
_update_row(attribute_names, "touch")
-
end
-
-
3
def _update_row(attribute_names, attempted_action = "update")
-
2044
self.class._update_record(
-
attributes_with_values(attribute_names),
-
@primary_key => id_in_database
-
)
-
end
-
-
3
def create_or_update(**, &block)
-
15942
_raise_readonly_record_error if readonly?
-
15894
return false if destroyed?
-
15870
result = new_record? ? _create_record(&block) : _update_record(&block)
-
15684
result != false
-
end
-
-
# Updates the associated record with values matching those of the instance attributes.
-
# Returns the number of affected rows.
-
3
def _update_record(attribute_names = self.attribute_names)
-
3413
attribute_names = attributes_for_update(attribute_names)
-
-
3413
if attribute_names.empty?
-
1518
affected_rows = 0
-
1518
@_trigger_update_callback = true
-
else
-
1895
affected_rows = _update_row(attribute_names)
-
1854
@_trigger_update_callback = affected_rows == 1
-
end
-
-
3372
@previously_new_record = false
-
-
3372
yield(self) if block_given?
-
-
3372
affected_rows
-
end
-
-
# Creates a record with values matching those of the instance attributes
-
# and returns its id.
-
3
def _create_record(attribute_names = self.attribute_names)
-
12437
attribute_names = attributes_for_create(attribute_names)
-
-
12437
new_id = self.class._insert_record(
-
attributes_with_values(attribute_names)
-
)
-
-
12384
self.id ||= new_id if @primary_key
-
-
12384
@new_record = false
-
12384
@previously_new_record = true
-
-
12384
yield(self) if block_given?
-
-
12384
id
-
end
-
-
3
def verify_readonly_attribute(name)
-
515
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
-
end
-
-
3
def _raise_record_not_destroyed
-
18
@_association_destroy_exception ||= nil
-
18
raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", self)
-
ensure
-
18
@_association_destroy_exception = nil
-
end
-
-
3
def _raise_readonly_record_error
-
51
raise ReadOnlyRecord, "#{self.class} is marked as readonly"
-
end
-
-
3
def _raise_record_not_touched_error
-
6
raise ActiveRecordError, <<~MSG.squish
-
Cannot touch on a new or destroyed record object. Consider using
-
persisted?, new_record?, or destroyed? before touching.
-
MSG
-
end
-
-
# The name of the method used to touch a +belongs_to+ association when the
-
# +:touch+ option is used.
-
3
def belongs_to_touch_method
-
:touch
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record Query Cache
-
3
class QueryCache
-
3
module ClassMethods
-
# Enable the query cache within the block if Active Record is configured.
-
# If it's not, it will execute the given block.
-
3
def cache(&block)
-
161
if connected? || !configurations.empty?
-
161
connection.cache(&block)
-
else
-
yield
-
end
-
end
-
-
# Disable the query cache within the block if Active Record is configured.
-
# If it's not, it will execute the given block.
-
3
def uncached(&block)
-
1137
if connected? || !configurations.empty?
-
1137
connection.uncached(&block)
-
else
-
yield
-
end
-
end
-
end
-
-
3
def self.run
-
68
pools = []
-
-
68
ActiveRecord::Base.connection_handlers.each do |key, handler|
-
1235
pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
-
end
-
-
68
pools
-
end
-
-
3
def self.complete(pools)
-
649
pools.each { |pool| pool.disable_query_cache! }
-
-
68
ActiveRecord::Base.connection_handlers.each do |_, handler|
-
73
handler.connection_pool_list.each do |pool|
-
581
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
-
end
-
end
-
end
-
-
3
def self.install_executor_hooks(executor = ActiveSupport::Executor)
-
68
executor.register_hook(self)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Querying
-
3
QUERYING_METHODS = [
-
:find, :find_by, :find_by!, :take, :take!, :first, :first!, :last, :last!,
-
:second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!,
-
:forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!,
-
:exists?, :any?, :many?, :none?, :one?,
-
:first_or_create, :first_or_create!, :first_or_initialize,
-
:find_or_create_by, :find_or_create_by!, :find_or_initialize_by,
-
:create_or_find_by, :create_or_find_by!,
-
:destroy_all, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by,
-
:find_each, :find_in_batches, :in_batches,
-
:select, :reselect, :order, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins,
-
:where, :rewhere, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly,
-
:and, :or, :annotate, :optimizer_hints, :extending,
-
:having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only,
-
:count, :average, :minimum, :maximum, :sum, :calculate,
-
:pluck, :pick, :ids, :strict_loading
-
].freeze # :nodoc:
-
3
delegate(*QUERYING_METHODS, to: :all)
-
-
# Executes a custom SQL query against your database and returns all the results. The results will
-
# be returned as an array, with the requested columns encapsulated as attributes of the model you call
-
# this method from. For example, if you call <tt>Product.find_by_sql</tt>, then the results will be returned in
-
# a +Product+ object with the attributes you specified in the SQL query.
-
#
-
# If you call a complicated SQL query which spans multiple tables, the columns specified by the
-
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
-
# table.
-
#
-
# The +sql+ parameter is a full SQL query as a string. It will be called as is; there will be
-
# no database agnostic conversions performed. This should be a last resort because using
-
# database-specific terms will lock you into using that particular database engine, or require you to
-
# change your call if you switch engines.
-
#
-
# # A simple SQL query spanning multiple tables
-
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
-
# # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "author"=>"Quentin"}>, ...]
-
#
-
# You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>:
-
#
-
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
-
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
-
3
def find_by_sql(sql, binds = [], preparable: nil, &block)
-
29797
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
-
29768
column_types = result_set.column_types
-
-
29768
unless column_types.empty?
-
8133
column_types = column_types.reject { |k, _| attribute_types.key?(k) }
-
end
-
-
29768
message_bus = ActiveSupport::Notifications.instrumenter
-
-
29768
payload = {
-
record_count: result_set.length,
-
class_name: name
-
}
-
-
29768
message_bus.instrument("instantiation.active_record", payload) do
-
29768
if result_set.includes_column?(inheritance_column)
-
38867
result_set.map { |record| instantiate(record, column_types, &block) }
-
else
-
# Instantiate a homogeneous set
-
234118
result_set.map { |record| instantiate_instance_of(self, record, column_types, &block) }
-
end
-
end
-
end
-
-
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
-
# The use of this method should be restricted to complicated SQL queries that can't be executed
-
# using the ActiveRecord::Calculations class methods. Look into those before using this method,
-
# as it could lock you into a specific database engine or require a code change to switch
-
# database engines.
-
#
-
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
-
# # => 12
-
#
-
# ==== Parameters
-
#
-
# * +sql+ - An SQL statement which should return a count query from the database, see the example above.
-
3
def count_by_sql(sql)
-
24
connection.select_value(sanitize_sql(sql), "#{name} Count").to_i
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_record"
-
require "rails"
-
require "active_support/core_ext/object/try"
-
require "active_model/railtie"
-
-
# For now, action_controller must always be present with
-
# Rails, so let's make sure that it gets required before
-
# here. This is needed for correctly setting up the middleware.
-
# In the future, this might become an optional require.
-
require "action_controller/railtie"
-
-
module ActiveRecord
-
# = Active Record Railtie
-
class Railtie < Rails::Railtie # :nodoc:
-
config.active_record = ActiveSupport::OrderedOptions.new
-
-
config.app_generators.orm :active_record, migration: true,
-
timestamps: true
-
-
config.action_dispatch.rescue_responses.merge!(
-
"ActiveRecord::RecordNotFound" => :not_found,
-
"ActiveRecord::StaleObjectError" => :conflict,
-
"ActiveRecord::RecordInvalid" => :unprocessable_entity,
-
"ActiveRecord::RecordNotSaved" => :unprocessable_entity
-
)
-
-
config.active_record.use_schema_cache_dump = true
-
config.active_record.maintain_test_schema = true
-
config.active_record.has_many_inversing = false
-
-
config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new
-
config.active_record.sqlite3.represent_boolean_as_integer = nil
-
-
config.eager_load_namespaces << ActiveRecord
-
-
rake_tasks do
-
namespace :db do
-
task :load_config do
-
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
-
-
if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT)
-
if engine.paths["db/migrate"].existent
-
ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths["db/migrate"].to_a
-
end
-
end
-
end
-
end
-
-
load "active_record/railties/databases.rake"
-
end
-
-
# When loading console, force ActiveRecord::Base to be loaded
-
# to avoid cross references when loading a constant for the
-
# first time. Also, make it output to STDERR.
-
console do |app|
-
require "active_record/railties/console_sandbox" if app.sandbox?
-
require "active_record/base"
-
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
-
console = ActiveSupport::Logger.new(STDERR)
-
console.level = Rails.logger.level
-
Rails.logger.extend ActiveSupport::Logger.broadcast console
-
end
-
ActiveRecord::Base.verbose_query_logs = false
-
end
-
-
runner do
-
require "active_record/base"
-
end
-
-
initializer "active_record.initialize_timezone" do
-
ActiveSupport.on_load(:active_record) do
-
self.time_zone_aware_attributes = true
-
self.default_timezone = :utc
-
end
-
end
-
-
initializer "active_record.logger" do
-
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
-
end
-
-
initializer "active_record.backtrace_cleaner" do
-
ActiveSupport.on_load(:active_record) { LogSubscriber.backtrace_cleaner = ::Rails.backtrace_cleaner }
-
end
-
-
initializer "active_record.migration_error" do |app|
-
if config.active_record.delete(:migration_error) == :page_load
-
config.app_middleware.insert_after ::ActionDispatch::Callbacks,
-
ActiveRecord::Migration::CheckPending,
-
file_watcher: app.config.file_watcher
-
end
-
end
-
-
initializer "active_record.database_selector" do
-
if options = config.active_record.delete(:database_selector)
-
resolver = config.active_record.delete(:database_resolver)
-
operations = config.active_record.delete(:database_resolver_context)
-
config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
-
end
-
end
-
-
initializer "Check for cache versioning support" do
-
config.after_initialize do |app|
-
ActiveSupport.on_load(:active_record) do
-
if app.config.active_record.cache_versioning && Rails.cache
-
unless Rails.cache.class.try(:supports_cache_versioning?)
-
raise <<-end_error
-
-
You're using a cache store that doesn't support native cache versioning.
-
Your best option is to upgrade to a newer version of #{Rails.cache.class}
-
that supports cache versioning (#{Rails.cache.class}.supports_cache_versioning? #=> true).
-
-
Next best, switch to a different cache store that does support cache versioning:
-
https://guides.rubyonrails.org/caching_with_rails.html#cache-stores.
-
-
To keep using the current cache store, you can turn off cache versioning entirely:
-
-
config.active_record.cache_versioning = false
-
-
end_error
-
end
-
end
-
end
-
end
-
end
-
-
initializer "active_record.check_schema_cache_dump" do
-
if config.active_record.delete(:use_schema_cache_dump)
-
config.after_initialize do |app|
-
ActiveSupport.on_load(:active_record) do
-
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
-
-
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
-
db_config.name,
-
schema_cache_path: db_config&.schema_cache_path
-
)
-
-
cache = ActiveRecord::ConnectionAdapters::SchemaCache.load_from(filename)
-
next if cache.nil?
-
-
current_version = ActiveRecord::Migrator.current_version
-
next if current_version.nil?
-
-
if cache.version != current_version
-
warn "Ignoring #{filename} because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
-
next
-
end
-
-
connection_pool.set_schema_cache(cache.dup)
-
end
-
end
-
end
-
end
-
-
initializer "active_record.define_attribute_methods" do |app|
-
config.after_initialize do
-
ActiveSupport.on_load(:active_record) do
-
if app.config.eager_load
-
descendants.each do |model|
-
# SchemaMigration and InternalMetadata both override `table_exists?`
-
# to bypass the schema cache, so skip them to avoid the extra queries.
-
next if model._internal?
-
-
# If there's no connection yet, or the schema cache doesn't have the columns
-
# hash for the model cached, `define_attribute_methods` would trigger a query.
-
next unless model.connected? && model.connection.schema_cache.columns_hash?(model.table_name)
-
-
model.define_attribute_methods
-
end
-
end
-
end
-
end
-
end
-
-
initializer "active_record.warn_on_records_fetched_greater_than" do
-
if config.active_record.warn_on_records_fetched_greater_than
-
ActiveSupport.on_load(:active_record) do
-
require "active_record/relation/record_fetch_warning"
-
end
-
end
-
end
-
-
initializer "active_record.set_configs" do |app|
-
ActiveSupport.on_load(:active_record) do
-
configs = app.config.active_record
-
-
represent_boolean_as_integer = configs.sqlite3.delete(:represent_boolean_as_integer)
-
-
unless represent_boolean_as_integer.nil?
-
ActiveSupport.on_load(:active_record_sqlite3adapter) do
-
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = represent_boolean_as_integer
-
end
-
end
-
-
configs.delete(:sqlite3)
-
-
configs.each do |k, v|
-
send "#{k}=", v
-
end
-
end
-
end
-
-
# This sets the database configuration from Configuration#database_configuration
-
# and then establishes the connection.
-
initializer "active_record.initialize_database" do
-
ActiveSupport.on_load(:active_record) do
-
self.connection_handlers = { writing_role => ActiveRecord::Base.default_connection_handler }
-
self.configurations = Rails.application.config.database_configuration
-
establish_connection
-
end
-
end
-
-
# Expose database runtime to controller for logging.
-
initializer "active_record.log_runtime" do
-
require "active_record/railties/controller_runtime"
-
ActiveSupport.on_load(:action_controller) do
-
include ActiveRecord::Railties::ControllerRuntime
-
end
-
end
-
-
initializer "active_record.set_reloader_hooks" do
-
ActiveSupport.on_load(:active_record) do
-
ActiveSupport::Reloader.before_class_unload do
-
if ActiveRecord::Base.connected?
-
ActiveRecord::Base.clear_cache!
-
ActiveRecord::Base.clear_reloadable_connections!
-
end
-
end
-
end
-
end
-
-
initializer "active_record.set_executor_hooks" do
-
ActiveRecord::QueryCache.install_executor_hooks
-
end
-
-
initializer "active_record.add_watchable_files" do |app|
-
path = app.paths["db"].first
-
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
-
end
-
-
initializer "active_record.clear_active_connections" do
-
config.after_initialize do
-
ActiveSupport.on_load(:active_record) do
-
# Ideally the application doesn't connect to the database during boot,
-
# but sometimes it does. In case it did, we want to empty out the
-
# connection pools so that a non-database-using process (e.g. a master
-
# process in a forking server model) doesn't retain a needless
-
# connection. If it was needed, the incremental cost of reestablishing
-
# this connection is trivial: the rest of the pool would need to be
-
# populated anyway.
-
-
clear_active_connections!
-
flush_idle_connections!
-
end
-
end
-
end
-
-
initializer "active_record.set_filter_attributes" do
-
ActiveSupport.on_load(:active_record) do
-
self.filter_attributes += Rails.application.config.filter_parameters
-
end
-
end
-
-
initializer "active_record.set_signed_id_verifier_secret" do
-
ActiveSupport.on_load(:active_record) do
-
self.signed_id_verifier_secret ||= -> { Rails.application.key_generator.generate_key("active_record/signed_id") }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
ActiveRecord::Base.connection.begin_transaction(joinable: false)
-
-
at_exit do
-
ActiveRecord::Base.connection.rollback_transaction
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/module/attr_internal"
-
require "active_record/log_subscriber"
-
-
module ActiveRecord
-
module Railties # :nodoc:
-
module ControllerRuntime #:nodoc:
-
extend ActiveSupport::Concern
-
-
module ClassMethods # :nodoc:
-
def log_process_action(payload)
-
messages, db_runtime = super, payload[:db_runtime]
-
messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime
-
messages
-
end
-
end
-
-
private
-
attr_internal :db_runtime
-
-
def process_action(action, *args)
-
# We also need to reset the runtime before each action
-
# because of queries in middleware or in cases we are streaming
-
# and it won't be cleaned up by the method below.
-
ActiveRecord::LogSubscriber.reset_runtime
-
super
-
end
-
-
def cleanup_view_runtime
-
if logger && logger.info? && ActiveRecord::Base.connected?
-
db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
-
self.db_runtime = (db_runtime || 0) + db_rt_before_render
-
runtime = super
-
db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime
-
self.db_runtime += db_rt_after_render
-
runtime - db_rt_after_render
-
else
-
super
-
end
-
end
-
-
def append_info_to_payload(payload)
-
super
-
if ActiveRecord::Base.connected?
-
payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module ReadonlyAttributes
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
3
class_attribute :_attr_readonly, instance_accessor: false, default: []
-
end
-
-
3
module ClassMethods
-
# Attributes listed as readonly will be used to create a new record but update operations will
-
# ignore these fields.
-
3
def attr_readonly(*attributes)
-
51
self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || [])
-
end
-
-
# Returns an array of all the attributes that have been specified as readonly.
-
3
def readonly_attributes
-
341
_attr_readonly
-
end
-
-
3
def readonly_attribute?(name) # :nodoc:
-
3652
_attr_readonly.include?(name)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/filters"
-
-
3
module ActiveRecord
-
# = Active Record Reflection
-
3
module Reflection # :nodoc:
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
6
class_attribute :_reflections, instance_writer: false, default: {}
-
6
class_attribute :aggregate_reflections, instance_writer: false, default: {}
-
end
-
-
3
class << self
-
3
def create(macro, name, scope, options, ar)
-
3701
reflection = reflection_class_for(macro).new(name, scope, options, ar)
-
3698
options[:through] ? ThroughReflection.new(reflection) : reflection
-
end
-
-
3
def add_reflection(ar, name, reflection)
-
3590
ar.clear_reflections_cache
-
3590
name = -name.to_s
-
3590
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
-
end
-
-
3
def add_aggregate_reflection(ar, name, reflection)
-
27
ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
-
end
-
-
3
private
-
3
def reflection_class_for(macro)
-
3701
case macro
-
when :composed_of
-
27
AggregateReflection
-
when :has_many
-
1946
HasManyReflection
-
when :has_one
-
420
HasOneReflection
-
when :belongs_to
-
1308
BelongsToReflection
-
else
-
raise "Unsupported Macro: #{macro}"
-
end
-
end
-
end
-
-
# \Reflection enables the ability to examine the associations and aggregations of
-
# Active Record classes and objects. This information, for example,
-
# can be used in a form builder that takes an Active Record object
-
# and creates input fields for all of the attributes depending on their type
-
# and displays the associations to other objects.
-
#
-
# MacroReflection class has info for AggregateReflection and AssociationReflection
-
# classes.
-
3
module ClassMethods
-
# Returns an array of AggregateReflection objects for all the aggregations in the class.
-
3
def reflect_on_all_aggregations
-
9
aggregate_reflections.values
-
end
-
-
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
-
#
-
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
-
#
-
3
def reflect_on_aggregation(aggregation)
-
48792
aggregate_reflections[aggregation.to_s]
-
end
-
-
# Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
-
#
-
# Account.reflections # => {"balance" => AggregateReflection}
-
#
-
3
def reflections
-
4582
@__reflections ||= begin
-
582
ref = {}
-
-
582
_reflections.each do |name, reflection|
-
3746
parent_reflection = reflection.parent_reflection
-
-
3746
if parent_reflection
-
465
parent_name = parent_reflection.name
-
465
ref[parent_name.to_s] = parent_reflection
-
else
-
3281
ref[name] = reflection
-
end
-
end
-
-
582
ref
-
end
-
end
-
-
# Returns an array of AssociationReflection objects for all the
-
# associations in the class. If you only want to reflect on a certain
-
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
-
# <tt>:belongs_to</tt>) as the first parameter.
-
#
-
# Example:
-
#
-
# Account.reflect_on_all_associations # returns an array of all associations
-
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
-
#
-
3
def reflect_on_all_associations(macro = nil)
-
1072
association_reflections = reflections.values
-
11828
association_reflections.select! { |reflection| reflection.macro == macro } if macro
-
1072
association_reflections
-
end
-
-
# Returns the AssociationReflection object for the +association+ (use the symbol).
-
#
-
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
-
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
-
#
-
3
def reflect_on_association(association)
-
333
reflections[association.to_s]
-
end
-
-
3
def _reflect_on_association(association) #:nodoc:
-
682410
_reflections[association.to_s]
-
end
-
-
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
-
3
def reflect_on_all_autosave_associations
-
60
reflections.values.select { |reflection| reflection.options[:autosave] }
-
end
-
-
3
def clear_reflections_cache # :nodoc:
-
3605
@__reflections = nil
-
end
-
end
-
-
# Holds all the methods that are shared between MacroReflection and ThroughReflection.
-
#
-
# AbstractReflection
-
# MacroReflection
-
# AggregateReflection
-
# AssociationReflection
-
# HasManyReflection
-
# HasOneReflection
-
# BelongsToReflection
-
# HasAndBelongsToManyReflection
-
# ThroughReflection
-
# PolymorphicReflection
-
# RuntimeReflection
-
3
class AbstractReflection # :nodoc:
-
3
def through_reflection?
-
9463
false
-
end
-
-
3
def table_name
-
1250
klass.table_name
-
end
-
-
# Returns a new, unsaved instance of the associated class. +attributes+ will
-
# be passed to the class's constructor.
-
3
def build_association(attributes, &block)
-
4292
klass.new(attributes, &block)
-
end
-
-
# Returns the class name for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
-
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
-
3
def class_name
-
3521
@class_name ||= -(options[:class_name]&.to_s || derive_class_name)
-
end
-
-
# Returns a list of scopes that should be applied for this Reflection
-
# object when querying the database.
-
3
def scopes
-
13975
scope ? [scope] : []
-
end
-
-
3
def join_scope(table, foreign_table, foreign_klass)
-
3139
predicate_builder = predicate_builder(table)
-
3139
scope_chain_items = join_scopes(table, predicate_builder)
-
3139
klass_scope = klass_join_scope(table, predicate_builder)
-
-
3139
if type
-
198
klass_scope.where!(type => foreign_klass.polymorphic_name)
-
end
-
-
3139
scope_chain_items.inject(klass_scope, &:merge!)
-
-
3139
primary_key = join_primary_key
-
3139
foreign_key = join_foreign_key
-
-
3139
klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
-
-
3139
if klass.finder_needs_type_condition?
-
192
klass_scope.where!(klass.send(:type_condition, table))
-
end
-
-
3139
klass_scope
-
end
-
-
3
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
-
6778
if scope
-
753
[scope_for(build_scope(table, predicate_builder, klass))]
-
else
-
6025
[]
-
end
-
end
-
-
3
def klass_join_scope(table, predicate_builder) # :nodoc:
-
3139
relation = build_scope(table, predicate_builder)
-
3139
klass.scope_for_association(relation)
-
end
-
-
3
def constraints
-
13975
chain.flat_map(&:scopes)
-
end
-
-
3
def counter_cache_column
-
28634
@counter_cache_column ||= if belongs_to?
-
23469
if options[:counter_cache] == true
-
24
-"#{active_record.name.demodulize.underscore.pluralize}_count"
-
23445
elsif options[:counter_cache]
-
33
-options[:counter_cache].to_s
-
end
-
else
-
520
-(options[:counter_cache]&.to_s || "#{name}_count")
-
end
-
end
-
-
3
def inverse_of
-
48772
return unless inverse_name
-
-
33941
@inverse_of ||= klass._reflect_on_association inverse_name
-
end
-
-
3
def check_validity_of_inverse!
-
230432
unless polymorphic?
-
228679
if has_inverse? && inverse_of.nil?
-
9
raise InverseOfAssociationNotFoundError.new(self)
-
end
-
end
-
end
-
-
# This shit is nasty. We need to avoid the following situation:
-
#
-
# * An associated record is deleted via record.destroy
-
# * Hence the callbacks run, and they find a belongs_to on the record with a
-
# :counter_cache options which points back at our owner. So they update the
-
# counter cache.
-
# * In which case, we must make sure to *not* update the counter cache, or else
-
# it will be decremented twice.
-
#
-
# Hence this method.
-
3
def inverse_which_updates_counter_cache
-
5381
return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
-
631
@inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
-
1550
inverse.counter_cache_column == counter_cache_column
-
end
-
end
-
3
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
-
-
3
def inverse_updates_counter_in_memory?
-
2466
inverse_of && inverse_which_updates_counter_cache == inverse_of
-
end
-
-
# Returns whether a counter cache should be used for this association.
-
#
-
# The counter_cache option must be given on either the owner or inverse
-
# association, and the column must be present on the owner.
-
3
def has_cached_counter?
-
4053
options[:counter_cache] ||
-
inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
-
active_record.has_attribute?(counter_cache_column)
-
end
-
-
3
def counter_must_be_updated_by_has_many?
-
2466
!inverse_updates_counter_in_memory? && has_cached_counter?
-
end
-
-
3
def alias_candidate(name)
-
486
"#{plural_name}_#{name}"
-
end
-
-
3
def chain
-
35441
collect_join_chain
-
end
-
-
3
def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
-
6585
Relation.create(
-
klass,
-
table: table,
-
predicate_builder: predicate_builder
-
)
-
end
-
-
3
def strict_loading?
-
5335
options[:strict_loading]
-
end
-
-
3
protected
-
3
def actual_source_reflection # FIXME: this is a horrible name
-
162
self
-
end
-
-
3
private
-
3
def predicate_builder(table)
-
5814
PredicateBuilder.new(TableMetadata.new(klass, table))
-
end
-
-
3
def primary_key(klass)
-
22995
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
-
end
-
end
-
-
# Base class for AggregateReflection and AssociationReflection. Objects of
-
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
-
3
class MacroReflection < AbstractReflection
-
# Returns the name of the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
-
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
-
3
attr_reader :name
-
-
3
attr_reader :scope
-
-
# Returns the hash of options used for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
-
# <tt>has_many :clients</tt> returns <tt>{}</tt>
-
3
attr_reader :options
-
-
3
attr_reader :active_record
-
-
3
attr_reader :plural_name # :nodoc:
-
-
3
def initialize(name, scope, options, active_record)
-
3952
@name = name
-
3952
@scope = scope
-
3952
@options = options
-
3952
@active_record = active_record
-
3952
@klass = options[:anonymous_class]
-
3952
@plural_name = active_record.pluralize_table_names ?
-
name.to_s.pluralize : name.to_s
-
end
-
-
3
def autosave=(autosave)
-
384
@options[:autosave] = autosave
-
384
parent_reflection = self.parent_reflection
-
384
if parent_reflection
-
66
parent_reflection.autosave = autosave
-
end
-
end
-
-
# Returns the class for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
-
# <tt>has_many :clients</tt> returns the Client class
-
#
-
# class Company < ActiveRecord::Base
-
# has_many :clients
-
# end
-
#
-
# Company.reflect_on_association(:clients).klass
-
# # => Client
-
#
-
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
-
# a new association object. Use +build_association+ or +create_association+
-
# instead. This allows plugins to hook into association object creation.
-
3
def klass
-
608804
@klass ||= compute_class(class_name)
-
end
-
-
3
def compute_class(name)
-
6
name.constantize
-
end
-
-
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
-
# and +other_aggregation+ has an options hash assigned to it.
-
3
def ==(other_aggregation)
-
2825
super ||
-
other_aggregation.kind_of?(self.class) &&
-
name == other_aggregation.name &&
-
!other_aggregation.options.nil? &&
-
active_record == other_aggregation.active_record
-
end
-
-
3
def scope_for(relation, owner = nil)
-
2597
relation.instance_exec(owner, &scope) || relation
-
end
-
-
3
private
-
3
def derive_class_name
-
3
name.to_s.camelize
-
end
-
end
-
-
# Holds all the metadata about an aggregation as it was specified in the
-
# Active Record class.
-
3
class AggregateReflection < MacroReflection #:nodoc:
-
3
def mapping
-
75
mapping = options[:mapping] || [name, name]
-
75
mapping.first.is_a?(Array) ? mapping : [mapping]
-
end
-
end
-
-
# Holds all the metadata about an association as it was specified in the
-
# Active Record class.
-
3
class AssociationReflection < MacroReflection #:nodoc:
-
3
def compute_class(name)
-
2683
if polymorphic?
-
6
raise ArgumentError, "Polymorphic associations do not support computing the class."
-
end
-
2677
active_record.send(:compute_type, name)
-
end
-
-
3
attr_reader :type, :foreign_type
-
3
attr_accessor :parent_reflection # Reflection
-
-
3
def initialize(name, scope, options, active_record)
-
3916
super
-
3916
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
-
3916
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
-
3916
@constructable = calculate_constructable(macro, options)
-
-
3916
if options[:class_name] && options[:class_name].class == Class
-
3
raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
-
end
-
end
-
-
3
def association_scope_cache(klass, owner, &block)
-
3218
key = self
-
3218
if polymorphic?
-
141
key = [key, owner._read_attribute(@foreign_type)]
-
end
-
3218
klass.cached_find_by_statement(key, &block)
-
end
-
-
3
def constructable? # :nodoc:
-
1701
@constructable
-
end
-
-
3
def join_table
-
27
@join_table ||= -(options[:join_table]&.to_s || derive_join_table)
-
end
-
-
3
def foreign_key
-
839058
@foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
-
end
-
-
3
def association_foreign_key
-
@association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key)
-
end
-
-
3
def association_primary_key(klass = nil)
-
107
primary_key(klass || self.klass)
-
end
-
-
3
def active_record_primary_key
-
22229
@active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
-
end
-
-
3
def join_primary_key(klass = nil)
-
26438
foreign_key
-
end
-
-
3
def join_foreign_key
-
21158
active_record_primary_key
-
end
-
-
3
def check_validity!
-
225473
check_validity_of_inverse!
-
end
-
-
3
def check_preloadable!
-
4919
return unless scope
-
-
543
unless scope.arity == 0
-
21
raise ArgumentError, <<-MSG.squish
-
The association scope '#{name}' is instance dependent (the scope
-
block takes an argument). Preloading instance dependent scopes is
-
not supported.
-
MSG
-
end
-
end
-
3
alias :check_eager_loadable! :check_preloadable!
-
-
3
def join_id_for(owner) # :nodoc:
-
3218
owner[join_foreign_key]
-
end
-
-
3
def through_reflection
-
nil
-
end
-
-
3
def source_reflection
-
2660
self
-
end
-
-
# A chain of reflections from this one back to the owner. For more see the explanation in
-
# ThroughReflection.
-
3
def collect_join_chain
-
26948
[self]
-
end
-
-
# This is for clearing cache on the reflection. Useful for tests that need to compare
-
# SQL queries on associations.
-
3
def clear_association_scope_cache # :nodoc:
-
15
klass.initialize_find_by_cache
-
end
-
-
3
def nested?
-
548
false
-
end
-
-
3
def has_scope?
-
5333
scope
-
end
-
-
3
def has_inverse?
-
229603
inverse_name
-
end
-
-
3
def polymorphic_inverse_of(associated_class)
-
870
if has_inverse?
-
57
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
-
51
inverse_relationship
-
else
-
6
raise InverseOfAssociationNotFoundError.new(self, associated_class)
-
end
-
end
-
end
-
-
# Returns the macro type.
-
#
-
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
-
3
def macro; raise NotImplementedError; end
-
-
# Returns whether or not this association reflection is for a collection
-
# association. Returns +true+ if the +macro+ is either +has_many+ or
-
# +has_and_belongs_to_many+, +false+ otherwise.
-
3
def collection?
-
213434
false
-
end
-
-
# Returns whether or not the association should be validated as part of
-
# the parent's validation.
-
#
-
# Unless you explicitly disable validation with
-
# <tt>validate: false</tt>, validation will take place when:
-
#
-
# * you explicitly enable validation; <tt>validate: true</tt>
-
# * you use autosave; <tt>autosave: true</tt>
-
# * the association is a +has_many+ association
-
3
def validate?
-
4582
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
-
end
-
-
# Returns +true+ if +self+ is a +belongs_to+ reflection.
-
118518
def belongs_to?; false; end
-
-
# Returns +true+ if +self+ is a +has_one+ reflection.
-
6177
def has_one?; false; end
-
-
3
def association_class; raise NotImplementedError; end
-
-
3
def polymorphic?
-
479338
options[:polymorphic]
-
end
-
-
3
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
-
3
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key]
-
-
3
def add_as_source(seed)
-
8829
seed
-
end
-
-
3
def add_as_polymorphic_through(reflection, seed)
-
96
seed + [PolymorphicReflection.new(self, reflection)]
-
end
-
-
3
def add_as_through(seed)
-
8718
seed + [self]
-
end
-
-
3
def extensions
-
17755
Array(options[:extend])
-
end
-
-
3
private
-
3
def calculate_constructable(macro, options)
-
2188
true
-
end
-
-
# Attempts to find the inverse association name automatically.
-
# If it cannot find a suitable inverse association name, it returns
-
# +nil+.
-
3
def inverse_name
-
278828
unless defined?(@inverse_name)
-
4980
@inverse_name = options.fetch(:inverse_of) { automatic_inverse_of }
-
end
-
-
278828
@inverse_name
-
end
-
-
# returns either +nil+ or the inverse association name that it finds.
-
3
def automatic_inverse_of
-
2422
if can_find_inverse_of_automatically?(self)
-
906
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
-
-
906
begin
-
906
reflection = klass._reflect_on_association(inverse_name)
-
rescue NameError
-
# Give up: we couldn't compute the klass type so we won't be able
-
# to find any associations either.
-
reflection = false
-
end
-
-
906
if valid_inverse_reflection?(reflection)
-
365
inverse_name
-
end
-
end
-
end
-
-
# Checks if the inverse reflection that is returned from the
-
# +automatic_inverse_of+ method is a valid reflection. We must
-
# make sure that the reflection's active_record name matches up
-
# with the current reflection's klass name.
-
3
def valid_inverse_reflection?(reflection)
-
906
reflection &&
-
klass <= reflection.active_record &&
-
can_find_inverse_of_automatically?(reflection)
-
end
-
-
# Checks to see if the reflection doesn't have any options that prevent
-
# us from being able to guess the inverse automatically. First, the
-
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
-
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
-
# Third, we must not have options such as <tt>:foreign_key</tt>
-
# which prevent us from correctly guessing the inverse association.
-
#
-
# Anything with a scope can additionally ruin our attempt at finding an
-
# inverse, so we exclude reflections with scopes.
-
3
def can_find_inverse_of_automatically?(reflection)
-
2745
reflection.options[:inverse_of] != false &&
-
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
-
4772
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
-
!reflection.scope
-
end
-
-
3
def derive_class_name
-
968
class_name = name.to_s
-
968
class_name = class_name.singularize if collection?
-
968
class_name.camelize
-
end
-
-
3
def derive_foreign_key
-
1423
if belongs_to?
-
600
"#{name}_id"
-
823
elsif options[:as]
-
108
"#{options[:as]}_id"
-
else
-
715
active_record.name.foreign_key
-
end
-
end
-
-
3
def derive_join_table
-
18
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
-
end
-
end
-
-
3
class HasManyReflection < AssociationReflection # :nodoc:
-
1022946
def macro; :has_many; end
-
-
27098
def collection?; true; end
-
-
3
def association_class
-
13171
if options[:through]
-
4292
Associations::HasManyThroughAssociation
-
else
-
8879
Associations::HasManyAssociation
-
end
-
end
-
end
-
-
3
class HasOneReflection < AssociationReflection # :nodoc:
-
85266
def macro; :has_one; end
-
-
1576
def has_one?; true; end
-
-
3
def association_class
-
2830
if options[:through]
-
303
Associations::HasOneThroughAssociation
-
else
-
2527
Associations::HasOneAssociation
-
end
-
end
-
-
3
private
-
3
def calculate_constructable(macro, options)
-
420
!options[:through]
-
end
-
end
-
-
3
class BelongsToReflection < AssociationReflection # :nodoc:
-
343710
def macro; :belongs_to; end
-
-
49366
def belongs_to?; true; end
-
-
3
def association_class
-
211994
if polymorphic?
-
1741
Associations::BelongsToPolymorphicAssociation
-
else
-
210253
Associations::BelongsToAssociation
-
end
-
end
-
-
# klass option is necessary to support loading polymorphic associations
-
3
def association_primary_key(klass = nil)
-
21990
if primary_key = options[:primary_key]
-
342
@association_primary_key ||= -primary_key.to_s
-
else
-
21648
primary_key(klass || self.klass)
-
end
-
end
-
-
3
def join_primary_key(klass = nil)
-
10796
polymorphic? ? association_primary_key(klass) : association_primary_key
-
end
-
-
3
def join_foreign_key
-
546504
foreign_key
-
end
-
-
3
def join_foreign_type
-
304
foreign_type
-
end
-
-
3
private
-
3
def can_find_inverse_of_automatically?(_)
-
736
!polymorphic? && super
-
end
-
-
3
def calculate_constructable(macro, options)
-
1308
!polymorphic?
-
end
-
end
-
-
3
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
-
1004
def macro; :has_and_belongs_to_many; end
-
-
3
def collection?
-
9
true
-
end
-
end
-
-
# Holds all the metadata about a :through association as it was specified
-
# in the Active Record class.
-
3
class ThroughReflection < AbstractReflection #:nodoc:
-
3
delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
-
:active_record_primary_key, :join_foreign_key, to: :source_reflection
-
-
3
def initialize(delegate_reflection)
-
878
@delegate_reflection = delegate_reflection
-
878
@klass = delegate_reflection.options[:anonymous_class]
-
878
@source_reflection_name = delegate_reflection.options[:source]
-
end
-
-
3
def through_reflection?
-
228
true
-
end
-
-
3
def klass
-
59602
@klass ||= delegate_reflection.compute_class(class_name).tap do |klass|
-
736
if !parent_reflection.is_a?(HasAndBelongsToManyReflection) &&
-
528
!(klass.reflections.key?(options[:through].to_s) ||
-
klass.reflections.key?(options[:through].to_s.pluralize)) &&
-
active_record.type_for_attribute(active_record.primary_key).type != :integer
-
6
raise NotImplementedError, <<~MSG.squish
-
In order to correctly type cast #{active_record}.#{active_record.primary_key},
-
#{klass} needs to define a :#{options[:through]} association.
-
MSG
-
end
-
end
-
end
-
-
# Returns the source of the through reflection. It checks both a singularized
-
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# class Tagging < ActiveRecord::Base
-
# belongs_to :post
-
# belongs_to :tag
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.source_reflection
-
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
-
#
-
3
def source_reflection
-
50608
through_reflection.klass._reflect_on_association(source_reflection_name)
-
end
-
-
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
-
# of a HasManyThrough or HasOneThrough association.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.through_reflection
-
# # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
-
#
-
3
def through_reflection
-
83901
active_record._reflect_on_association(options[:through])
-
end
-
-
# Returns an array of reflections which are involved in this association. Each item in the
-
# array corresponds to a table which will be part of the query for this association.
-
#
-
# The chain is built by recursively calling #chain on the source reflection and the through
-
# reflection. The base case for the recursion is a normal association, which just returns
-
# [self] as its #chain.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.chain
-
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
-
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
-
#
-
3
def collect_join_chain
-
8493
collect_join_reflections [self]
-
end
-
-
# This is for clearing cache on the reflection. Useful for tests that need to compare
-
# SQL queries on associations.
-
3
def clear_association_scope_cache # :nodoc:
-
6
delegate_reflection.clear_association_scope_cache
-
6
source_reflection.clear_association_scope_cache
-
6
through_reflection.clear_association_scope_cache
-
end
-
-
3
def scopes
-
source_reflection.scopes + super
-
end
-
-
3
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
-
1187
source_reflection.join_scopes(table, predicate_builder, klass) + super
-
end
-
-
3
def has_scope?
-
1341
scope || options[:source_type] ||
-
source_reflection.has_scope? ||
-
through_reflection.has_scope?
-
end
-
-
# A through association is nested if there would be more than one join table
-
3
def nested?
-
2666
source_reflection.through_reflection? || through_reflection.through_reflection?
-
end
-
-
# We want to use the klass from this reflection, rather than just delegate straight to
-
# the source_reflection, because the source_reflection may be polymorphic. We still
-
# need to respect the source_reflection's :primary_key option, though.
-
3
def association_primary_key(klass = nil)
-
# Get the "actual" source reflection if the immediate source reflection has a
-
# source reflection itself
-
162
if primary_key = actual_source_reflection.options[:primary_key]
-
27
@association_primary_key ||= -primary_key.to_s
-
else
-
135
primary_key(klass || self.klass)
-
end
-
end
-
-
3
def join_primary_key(klass = self.klass)
-
3573
source_reflection.join_primary_key(klass)
-
end
-
-
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.source_reflection_names
-
# # => [:tag, :tags]
-
#
-
3
def source_reflection_names
-
options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
-
end
-
-
3
def source_reflection_name # :nodoc:
-
50608
return @source_reflection_name if @source_reflection_name
-
-
228
names = [name.to_s.singularize, name].collect(&:to_sym).uniq
-
228
names = names.find_all { |n|
-
405
through_reflection.klass._reflect_on_association(n)
-
}
-
-
228
if names.length > 1
-
raise AmbiguousSourceReflectionForThroughAssociation.new(
-
active_record.name,
-
macro,
-
name,
-
options,
-
source_reflection_names
-
)
-
end
-
-
228
@source_reflection_name = names.first
-
end
-
-
3
def source_options
-
source_reflection.options
-
end
-
-
3
def through_options
-
through_reflection.options
-
end
-
-
3
def check_validity!
-
4977
if through_reflection.nil?
-
3
raise HasManyThroughAssociationNotFoundError.new(active_record, self)
-
end
-
-
4974
if through_reflection.polymorphic?
-
6
if has_one?
-
3
raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
-
else
-
3
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
-
end
-
end
-
-
4968
if source_reflection.nil?
-
raise HasManyThroughSourceAssociationNotFoundError.new(self)
-
end
-
-
4968
if options[:source_type] && !source_reflection.polymorphic?
-
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
-
end
-
-
4968
if source_reflection.polymorphic? && options[:source_type].nil?
-
3
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
-
end
-
-
4965
if has_one? && through_reflection.collection?
-
3
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
-
end
-
-
4962
if parent_reflection.nil?
-
2331
reflections = active_record.reflections.keys.map(&:to_sym)
-
-
2331
if reflections.index(through_reflection.name) > reflections.index(name)
-
3
raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
-
end
-
end
-
-
4959
check_validity_of_inverse!
-
end
-
-
3
def constraints
-
2950
scope_chain = source_reflection.constraints
-
2950
scope_chain << scope if scope
-
2950
scope_chain
-
end
-
-
3
def add_as_source(seed)
-
321
collect_join_reflections seed
-
end
-
-
3
def add_as_polymorphic_through(reflection, seed)
-
36
collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
-
end
-
-
3
def add_as_through(seed)
-
300
collect_join_reflections(seed + [self])
-
end
-
-
3
protected
-
3
def actual_source_reflection # FIXME: this is a horrible name
-
165
source_reflection.actual_source_reflection
-
end
-
-
3
private
-
3
attr_reader :delegate_reflection
-
-
3
def collect_join_reflections(seed)
-
9150
a = source_reflection.add_as_source seed
-
9150
if options[:source_type]
-
132
through_reflection.add_as_polymorphic_through self, a
-
else
-
9018
through_reflection.add_as_through a
-
end
-
end
-
-
961
def inverse_name; delegate_reflection.send(:inverse_name); end
-
-
3
def derive_class_name
-
# get the class_name of the belongs_to association of the through reflection
-
634
options[:source_type] || source_reflection.class_name
-
end
-
-
3
delegate_methods = AssociationReflection.public_instance_methods -
-
public_instance_methods
-
-
3
delegate(*delegate_methods, to: :delegate_reflection)
-
end
-
-
3
class PolymorphicReflection < AbstractReflection # :nodoc:
-
3
delegate :klass, :scope, :plural_name, :type, :join_primary_key, :join_foreign_key,
-
:scope_for, to: :@reflection
-
-
3
def initialize(reflection, previous_reflection)
-
132
@reflection = reflection
-
132
@previous_reflection = previous_reflection
-
end
-
-
3
def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
-
18
scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
-
18
scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
-
end
-
-
3
def constraints
-
42
@reflection.constraints + [source_type_scope]
-
end
-
-
3
private
-
3
def source_type_scope
-
60
type = @previous_reflection.foreign_type
-
60
source_type = @previous_reflection.options[:source_type]
-
120
lambda { |object| where(type => source_type) }
-
end
-
end
-
-
3
class RuntimeReflection < AbstractReflection # :nodoc:
-
3
delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
-
-
3
def initialize(reflection, association)
-
11025
@reflection = reflection
-
11025
@association = association
-
end
-
-
3
def klass
-
29337
@association.klass
-
end
-
-
3
def aliased_table
-
13454
klass.arel_table
-
end
-
-
3
def join_primary_key(klass = self.klass)
-
11025
@reflection.join_primary_key(klass)
-
end
-
-
2432
def all_includes; yield; end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record \Relation
-
3
class Relation
-
3
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
-
:order, :joins, :left_outer_joins, :references,
-
:extending, :unscope, :optimizer_hints, :annotate]
-
-
3
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
-
:reverse_order, :distinct, :create_with, :skip_query_cache]
-
-
3
CLAUSE_METHODS = [:where, :having, :from]
-
3
INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
-
-
3
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
-
-
3
include Enumerable
-
3
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
-
-
3
attr_reader :table, :klass, :loaded, :predicate_builder
-
3
attr_accessor :skip_preloading_value
-
3
alias :model :klass
-
3
alias :loaded? :loaded
-
3
alias :locked? :lock_value
-
-
3
def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
-
125255
@klass = klass
-
125255
@table = table
-
125255
@values = values
-
125255
@loaded = false
-
125255
@predicate_builder = predicate_builder
-
125255
@delegate_to_klass = false
-
end
-
-
3
def initialize_copy(other)
-
93533
@values = @values.dup
-
93533
reset
-
end
-
-
3
def arel_attribute(name) # :nodoc:
-
3
table[name]
-
end
-
3
deprecate :arel_attribute
-
-
3
def bind_attribute(name, value) # :nodoc:
-
523
if reflection = klass._reflect_on_association(name)
-
24
name = reflection.foreign_key
-
24
value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
-
end
-
-
523
attr = table[name]
-
523
bind = predicate_builder.build_bind_attribute(attr.name, value)
-
523
yield attr, bind
-
end
-
-
# Initializes new record from relation while maintaining the current
-
# scope.
-
#
-
# Expects arguments in the same format as {ActiveRecord::Base.new}[rdoc-ref:Core.new].
-
#
-
# users = User.where(name: 'DHH')
-
# user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
-
#
-
# You can also pass a block to new with the new record as argument:
-
#
-
# user = users.new { |user| user.name = 'Oscar' }
-
# user.name # => Oscar
-
3
def new(attributes = nil, &block)
-
160
block = _deprecated_scope_block("new", &block)
-
320
scoping { klass.new(attributes, &block) }
-
end
-
-
3
alias build new
-
-
# Tries to create a new record with the same scoped attributes
-
# defined in the relation. Returns the initialized object if validation fails.
-
#
-
# Expects arguments in the same format as
-
# {ActiveRecord::Base.create}[rdoc-ref:Persistence::ClassMethods#create].
-
#
-
# ==== Examples
-
#
-
# users = User.where(name: 'Oscar')
-
# users.create # => #<User id: 3, name: "Oscar", ...>
-
#
-
# users.create(name: 'fxn')
-
# users.create # => #<User id: 4, name: "fxn", ...>
-
#
-
# users.create { |user| user.name = 'tenderlove' }
-
# # => #<User id: 5, name: "tenderlove", ...>
-
#
-
# users.create(name: nil) # validation on name
-
# # => #<User id: nil, name: nil, ...>
-
3
def create(attributes = nil, &block)
-
81
if attributes.is_a?(Array)
-
9
attributes.collect { |attr| create(attr, &block) }
-
else
-
78
block = _deprecated_scope_block("create", &block)
-
156
scoping { klass.create(attributes, &block) }
-
end
-
end
-
-
# Similar to #create, but calls
-
# {create!}[rdoc-ref:Persistence::ClassMethods#create!]
-
# on the base class. Raises an exception if a validation error occurs.
-
#
-
# Expects arguments in the same format as
-
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
-
3
def create!(attributes = nil, &block)
-
124
if attributes.is_a?(Array)
-
18
attributes.collect { |attr| create!(attr, &block) }
-
else
-
118
block = _deprecated_scope_block("create!", &block)
-
236
scoping { klass.create!(attributes, &block) }
-
end
-
end
-
-
3
def first_or_create(attributes = nil, &block) # :nodoc:
-
33
first || create(attributes, &block)
-
end
-
-
3
def first_or_create!(attributes = nil, &block) # :nodoc:
-
36
first || create!(attributes, &block)
-
end
-
-
3
def first_or_initialize(attributes = nil, &block) # :nodoc:
-
24
first || new(attributes, &block)
-
end
-
-
# Finds the first record with the given attributes, or creates a record
-
# with the attributes if one is not found:
-
#
-
# # Find the first user named "Penélope" or create a new one.
-
# User.find_or_create_by(first_name: 'Penélope')
-
# # => #<User id: 1, first_name: "Penélope", last_name: nil>
-
#
-
# # Find the first user named "Penélope" or create a new one.
-
# # We already have one so the existing record will be returned.
-
# User.find_or_create_by(first_name: 'Penélope')
-
# # => #<User id: 1, first_name: "Penélope", last_name: nil>
-
#
-
# # Find the first user named "Scarlett" or create a new one with
-
# # a particular last name.
-
# User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
-
# # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
-
#
-
# This method accepts a block, which is passed down to #create. The last example
-
# above can be alternatively written this way:
-
#
-
# # Find the first user named "Scarlett" or create a new one with a
-
# # different last name.
-
# User.find_or_create_by(first_name: 'Scarlett') do |user|
-
# user.last_name = 'Johansson'
-
# end
-
# # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
-
#
-
# This method always returns a record, but if creation was attempted and
-
# failed due to validation errors it won't be persisted, you get what
-
# #create returns in such situation.
-
#
-
# Please note <b>this method is not atomic</b>, it runs first a SELECT, and if
-
# there are no results an INSERT is attempted. If there are other threads
-
# or processes there is a race condition between both calls and it could
-
# be the case that you end up with two similar records.
-
#
-
# If this might be a problem for your application, please see #create_or_find_by.
-
3
def find_or_create_by(attributes, &block)
-
18
find_by(attributes) || create(attributes, &block)
-
end
-
-
# Like #find_or_create_by, but calls
-
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
-
# is raised if the created record is invalid.
-
3
def find_or_create_by!(attributes, &block)
-
6
find_by(attributes) || create!(attributes, &block)
-
end
-
-
# Attempts to create a record with the given attributes in a table that has a unique constraint
-
# on one or several of its columns. If a row already exists with one or several of these
-
# unique constraints, the exception such an insertion would normally raise is caught,
-
# and the existing record with those attributes is found using #find_by!.
-
#
-
# This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT
-
# and the INSERT, as that method needs to first query the table, then attempt to insert a row
-
# if none is found.
-
#
-
# There are several drawbacks to #create_or_find_by, though:
-
#
-
# * The underlying table must have the relevant columns defined with unique constraints.
-
# * A unique constraint violation may be triggered by only one, or at least less than all,
-
# of the given attributes. This means that the subsequent #find_by! may fail to find a
-
# matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception,
-
# rather than a record with the given attributes.
-
# * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
-
# we actually have another race condition between INSERT -> SELECT, which can be triggered
-
# if a DELETE between those two statements is run by another client. But for most applications,
-
# that's a significantly less likely condition to hit.
-
# * It relies on exception handling to handle control flow, which may be marginally slower.
-
# * The primary key may auto-increment on each create, even if it fails. This can accelerate
-
# the problem of running out of integers, if the underlying table is still stuck on a primary
-
# key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
-
# to this problem).
-
#
-
# This method will return a record if all given attributes are covered by unique constraints
-
# (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
-
# and failed due to validation errors it won't be persisted, you get what #create returns in
-
# such situation.
-
3
def create_or_find_by(attributes, &block)
-
36
transaction(requires_new: true) { create(attributes, &block) }
-
rescue ActiveRecord::RecordNotUnique
-
9
find_by!(attributes)
-
end
-
-
# Like #create_or_find_by, but calls
-
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
-
# is raised if the created record is invalid.
-
3
def create_or_find_by!(attributes, &block)
-
36
transaction(requires_new: true) { create!(attributes, &block) }
-
rescue ActiveRecord::RecordNotUnique
-
9
find_by!(attributes)
-
end
-
-
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
-
# instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
-
3
def find_or_initialize_by(attributes, &block)
-
269
find_by(attributes) || new(attributes, &block)
-
end
-
-
# Runs EXPLAIN on the query or queries triggered by this relation and
-
# returns the result as a string. The string is formatted imitating the
-
# ones printed by the database shell.
-
#
-
# Note that this method actually runs the queries, since the results of some
-
# are needed by the next ones when eager loading is going on.
-
#
-
# Please see further details in the
-
# {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
-
3
def explain
-
24
exec_explain(collecting_queries_for_explain { exec_queries })
-
end
-
-
# Converts relation objects to Array.
-
3
def to_ary
-
8131
records.dup
-
end
-
3
alias to_a to_ary
-
-
3
def records # :nodoc:
-
24779
load
-
24699
@records
-
end
-
-
# Serializes the relation objects Array.
-
3
def encode_with(coder)
-
6
coder.represent_seq(nil, records)
-
end
-
-
# Returns size of the records.
-
3
def size
-
231
loaded? ? @records.length : count(:all)
-
end
-
-
# Returns true if there are no records.
-
3
def empty?
-
206
return @records.empty? if loaded?
-
194
!exists?
-
end
-
-
# Returns true if there are no records.
-
3
def none?
-
45
return super if block_given?
-
21
empty?
-
end
-
-
# Returns true if there are any records.
-
3
def any?
-
126
return super if block_given?
-
69
!empty?
-
end
-
-
# Returns true if there is exactly one record.
-
3
def one?
-
36
return super if block_given?
-
27
limit_value ? records.one? : size == 1
-
end
-
-
# Returns true if there is more than one record.
-
3
def many?
-
66
return super if block_given?
-
54
limit_value ? records.many? : size > 1
-
end
-
-
# Returns a stable cache key that can be used to identify this query.
-
# The cache key is built with a fingerprint of the SQL query.
-
#
-
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
-
# # => "products/query-1850ab3d302391b85b8693e941286659"
-
#
-
# If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
-
# in Rails 6.0 and earlier, the cache key will also include a version.
-
#
-
# ActiveRecord::Base.collection_cache_versioning = false
-
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
-
# # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
-
#
-
# You can also pass a custom timestamp column to fetch the timestamp of the
-
# last updated record.
-
#
-
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
-
3
def cache_key(timestamp_column = "updated_at")
-
93
@cache_keys ||= {}
-
93
@cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
-
end
-
-
3
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
-
75
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
-
75
key = "#{klass.model_name.cache_key}/query-#{query_signature}"
-
-
75
if collection_cache_versioning
-
9
key
-
else
-
66
"#{key}-#{compute_cache_version(timestamp_column)}"
-
end
-
end
-
3
private :compute_cache_key
-
-
# Returns a cache version that can be used together with the cache key to form
-
# a recyclable caching scheme. The cache version is built with the number of records
-
# matching the query, and the timestamp of the last updated record. When a new record
-
# comes to match the query, or any of the existing records is updated or deleted,
-
# the cache version changes.
-
#
-
# If the collection is loaded, the method will iterate through the records
-
# to generate the timestamp, otherwise it will trigger one SQL query like:
-
#
-
# SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
-
3
def cache_version(timestamp_column = :updated_at)
-
12
if collection_cache_versioning
-
9
@cache_versions ||= {}
-
9
@cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
-
end
-
end
-
-
3
def compute_cache_version(timestamp_column) # :nodoc:
-
72
timestamp_column = timestamp_column.to_s
-
-
72
if loaded? || distinct_value
-
18
size = records.size
-
18
if size > 0
-
81
timestamp = records.map { |record| record.read_attribute(timestamp_column) }.max
-
end
-
else
-
54
collection = eager_loading? ? apply_join_dependency : self
-
-
54
column = connection.visitor.compile(table[timestamp_column])
-
54
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
-
-
54
if collection.has_limit_or_offset?
-
12
query = collection.select("#{column} AS collection_cache_key_timestamp")
-
12
subquery_alias = "subquery_for_cache_key"
-
12
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
-
12
arel = query.build_subquery(subquery_alias, select_values % subquery_column)
-
else
-
42
query = collection.unscope(:order)
-
42
query.select_values = [select_values % column]
-
42
arel = query.arel
-
end
-
-
54
size, timestamp = connection.select_rows(arel, nil).first
-
-
51
if size
-
51
column_type = klass.type_for_attribute(timestamp_column)
-
51
timestamp = column_type.deserialize(timestamp)
-
else
-
size = 0
-
end
-
end
-
-
69
if timestamp
-
60
"#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
-
else
-
9
"#{size}"
-
end
-
end
-
3
private :compute_cache_version
-
-
# Returns a cache key along with the version.
-
3
def cache_key_with_version
-
6
if version = cache_version
-
3
"#{cache_key}-#{version}"
-
else
-
3
cache_key
-
end
-
end
-
-
# Scope all queries to the current scope.
-
#
-
# Comment.where(post_id: 1).scoping do
-
# Comment.first
-
# end
-
# # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
-
#
-
# Please check unscoped if you want to remove all previous scopes (including
-
# the default_scope) during the execution of a block.
-
3
def scoping
-
146545
already_in_scope? ? yield : _scoping(self) { yield }
-
end
-
-
3
def _exec_scope(name, *args, &block) # :nodoc:
-
798
@delegate_to_klass = true
-
1596
_scoping(_deprecated_spawn(name)) { instance_exec(*args, &block) || self }
-
ensure
-
798
@delegate_to_klass = false
-
end
-
-
# Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
-
# statement and sends it straight to the database. It does not instantiate the involved models and it does not
-
# trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
-
# Active Record's normal type casting and serialization. Returns the number of rows affected.
-
#
-
# Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
-
#
-
# ==== Parameters
-
#
-
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
-
#
-
# ==== Examples
-
#
-
# # Update all customers with the given attributes
-
# Customer.update_all wants_email: true
-
#
-
# # Update all books with 'Rails' in their title
-
# Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
-
#
-
# # Update all books that match conditions, but limit it to 5 ordered by date
-
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
-
#
-
# # Update all invoices and set the number column to its id value.
-
# Invoice.update_all('number = id')
-
3
def update_all(updates)
-
1338
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
-
-
1335
if eager_loading?
-
3
relation = apply_join_dependency
-
3
return relation.update_all(updates)
-
end
-
-
1332
stmt = Arel::UpdateManager.new
-
1332
stmt.table(arel.join_sources.empty? ? table : arel.source)
-
1332
stmt.key = table[primary_key]
-
1332
stmt.take(arel.limit)
-
1332
stmt.offset(arel.offset)
-
1332
stmt.order(*arel.orders)
-
1332
stmt.wheres = arel.constraints
-
-
1332
if updates.is_a?(Hash)
-
1266
if klass.locking_enabled? &&
-
!updates.key?(klass.locking_column) &&
-
!updates.key?(klass.locking_column.to_sym)
-
54
attr = table[klass.locking_column]
-
54
updates[attr.name] = _increment_attribute(attr)
-
end
-
1266
stmt.set _substitute_values(updates)
-
else
-
66
stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
-
end
-
-
1332
@klass.connection.update stmt, "#{@klass} Update All"
-
end
-
-
3
def update(id = :all, attributes) # :nodoc:
-
9
if id == :all
-
9
each { |record| record.update(attributes) }
-
else
-
6
klass.update(id, attributes)
-
end
-
end
-
-
# Updates the counters of the records in the current relation.
-
#
-
# ==== Parameters
-
#
-
# * +counter+ - A Hash containing the names of the fields to update as keys and the amount to update as values.
-
# * <tt>:touch</tt> option - Touch the timestamp columns when updating.
-
# * If attributes names are passed, they are updated along with update_at/on attributes.
-
#
-
# ==== Examples
-
#
-
# # For Posts by a given author increment the comment_count by 1.
-
# Post.where(author_id: author.id).update_counters(comment_count: 1)
-
3
def update_counters(counters)
-
923
touch = counters.delete(:touch)
-
-
923
updates = {}
-
923
counters.each do |counter_name, value|
-
995
attr = table[counter_name]
-
995
updates[attr.name] = _increment_attribute(attr, value)
-
end
-
-
923
if touch
-
114
names = touch if touch != true
-
114
names = Array.wrap(names)
-
114
options = names.extract_options!
-
114
touch_updates = klass.touch_attributes_with_time(*names, **options)
-
114
updates.merge!(touch_updates) unless touch_updates.empty?
-
end
-
-
923
update_all updates
-
end
-
-
# Touches all records in the current relation, setting the +updated_at+/+updated_on+ attributes to the current time or the time specified.
-
# It does not instantiate the involved models, and it does not trigger Active Record callbacks or validations.
-
# This method can be passed attribute names and an optional time argument.
-
# If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
-
# If no time argument is passed, the current time is used as default.
-
#
-
# === Examples
-
#
-
# # Touch all records
-
# Person.all.touch_all
-
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
-
#
-
# # Touch multiple records with a custom attribute
-
# Person.all.touch_all(:created_at)
-
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
-
#
-
# # Touch multiple records with a specified time
-
# Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
-
# # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
-
#
-
# # Touch records with scope
-
# Person.where(name: 'David').touch_all
-
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
-
3
def touch_all(*names, time: nil)
-
15
update_all klass.touch_attributes_with_time(*names, time: time)
-
end
-
-
# Destroys the records by instantiating each
-
# record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
-
# Each object's callbacks are executed (including <tt>:dependent</tt> association options).
-
# Returns the collection of objects that were destroyed; each will be frozen, to
-
# reflect that no changes should be made (since they can't be persisted).
-
#
-
# Note: Instantiation, callback execution, and deletion of each
-
# record can be time consuming when you're removing many records at
-
# once. It generates at least one SQL +DELETE+ query per record (or
-
# possibly more, to enforce your callbacks). If you want to delete many
-
# rows quickly, without concern for their associations or callbacks, use
-
# #delete_all instead.
-
#
-
# ==== Examples
-
#
-
# Person.where(age: 0..18).destroy_all
-
3
def destroy_all
-
130
records.each(&:destroy).tap { reset }
-
end
-
-
# Deletes the records without instantiating the records
-
# first, and hence not calling the {#destroy}[rdoc-ref:Persistence#destroy]
-
# method nor invoking callbacks.
-
# This is a single SQL DELETE statement that goes straight to the database, much more
-
# efficient than #destroy_all. Be careful with relations though, in particular
-
# <tt>:dependent</tt> rules defined on associations are not honored. Returns the
-
# number of rows affected.
-
#
-
# Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
-
#
-
# Both calls delete the affected posts all at once with a single DELETE statement.
-
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
-
# +after_destroy+ callbacks, use the #destroy_all method instead.
-
#
-
# If an invalid method is supplied, #delete_all raises an ActiveRecordError:
-
#
-
# Post.distinct.delete_all
-
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
-
3
def delete_all
-
2131
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
-
6393
value = @values[method]
-
6393
method == :distinct ? value : value&.any?
-
end
-
2131
if invalid_methods.any?
-
9
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
-
end
-
-
2122
if eager_loading?
-
3
relation = apply_join_dependency
-
3
return relation.delete_all
-
end
-
-
2119
stmt = Arel::DeleteManager.new
-
2119
stmt.from(arel.join_sources.empty? ? table : arel.source)
-
2119
stmt.key = table[primary_key]
-
2119
stmt.take(arel.limit)
-
2119
stmt.offset(arel.offset)
-
2119
stmt.order(*arel.orders)
-
2119
stmt.wheres = arel.constraints
-
-
2119
affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
-
-
2068
reset
-
2068
affected
-
end
-
-
# Finds and destroys all records matching the specified conditions.
-
# This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
-
# Returns the collection of objects that were destroyed.
-
#
-
# If no record is found, returns empty array.
-
#
-
# Person.destroy_by(id: 13)
-
# Person.destroy_by(name: 'Spartacus', rating: 4)
-
# Person.destroy_by("published_at < ?", 2.weeks.ago)
-
3
def destroy_by(*args)
-
6
where(*args).destroy_all
-
end
-
-
# Finds and deletes all records matching the specified conditions.
-
# This is short-hand for <tt>relation.where(condition).delete_all</tt>.
-
# Returns the number of rows affected.
-
#
-
# If no record is found, returns <tt>0</tt> as zero rows were affected.
-
#
-
# Person.delete_by(id: 13)
-
# Person.delete_by(name: 'Spartacus', rating: 4)
-
# Person.delete_by("published_at < ?", 2.weeks.ago)
-
3
def delete_by(*args)
-
400
where(*args).delete_all
-
end
-
-
# Causes the records to be loaded from the database if they have not
-
# been loaded already. You can use this if for some reason you need
-
# to explicitly load some records before actually using them. The
-
# return value is the relation itself, not the records.
-
#
-
# Post.where(published: true).load # => #<ActiveRecord::Relation>
-
3
def load(&block)
-
26922
unless loaded?
-
24226
@records = exec_queries(&block)
-
24146
@loaded = true
-
end
-
-
26842
self
-
end
-
-
# Forces reloading of relation.
-
3
def reload
-
6
reset
-
6
load
-
end
-
-
3
def reset
-
95672
@delegate_to_klass = false
-
95672
@_deprecated_scope_source = nil
-
95672
@to_sql = @arel = @loaded = @should_eager_load = nil
-
95672
@offsets = @take = nil
-
95672
@records = [].freeze
-
95672
self
-
end
-
-
# Returns sql statement for the relation.
-
#
-
# User.where(name: 'Oscar').to_sql
-
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
-
3
def to_sql
-
514
@to_sql ||= begin
-
466
if eager_loading?
-
9
apply_join_dependency do |relation, join_dependency|
-
9
relation = join_dependency.apply_column_aliases(relation)
-
9
relation.to_sql
-
end
-
else
-
457
conn = klass.connection
-
914
conn.unprepared_statement { conn.to_sql(arel) }
-
end
-
end
-
end
-
-
# Returns a hash of where conditions.
-
#
-
# User.where(name: 'Oscar').where_values_hash
-
# # => {name: "Oscar"}
-
3
def where_values_hash(relation_table_name = klass.table_name)
-
6731
where_clause.to_h(relation_table_name)
-
end
-
-
3
def scope_for_create
-
5317
hash = where_values_hash
-
5317
hash.delete(klass.inheritance_column) if klass.finder_needs_type_condition?
-
5452
create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
-
5317
hash
-
end
-
-
# Returns true if relation needs eager loading.
-
3
def eager_loading?
-
63790
@should_eager_load ||=
-
eager_load_values.any? ||
-
2940
includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
-
end
-
-
# Joins that are also marked for preloading. In which case we should just eager load them.
-
# Note that this is a naive implementation because we could have strings and symbols which
-
# represent the same association, but that aren't matched by this. Also, we could have
-
# nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
-
3
def joined_includes_values
-
2940
includes_values & joins_values
-
end
-
-
# Compares two relations for equality.
-
3
def ==(other)
-
597
case other
-
when Associations::CollectionProxy, AssociationRelation
-
27
self == other.records
-
when Relation
-
48
other.to_sql == to_sql
-
when Array
-
522
records == other
-
end
-
end
-
-
3
def pretty_print(q)
-
q.pp(records)
-
end
-
-
# Returns true if relation is blank.
-
3
def blank?
-
247
records.blank?
-
end
-
-
3
def values
-
61316
@values.dup
-
end
-
-
3
def inspect
-
18
subject = loaded? ? records : self
-
18
entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
-
-
18
entries[10] = "..." if entries.size == 11
-
-
18
"#<#{self.class.name} [#{entries.join(', ')}]>"
-
end
-
-
3
def empty_scope? # :nodoc:
-
4714
@values == klass.unscoped.values
-
end
-
-
3
def has_limit_or_offset? # :nodoc:
-
4695
limit_value || offset_value
-
end
-
-
3
def alias_tracker(joins = [], aliases = nil) # :nodoc:
-
13139
ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins, aliases)
-
end
-
-
3
class StrictLoadingScope # :nodoc:
-
3
def self.empty_scope?
-
3
true
-
end
-
-
3
def self.strict_loading_value
-
3
true
-
end
-
end
-
-
3
def preload_associations(records) # :nodoc:
-
24122
preload = preload_values
-
24122
preload += includes_values unless eager_loading?
-
24122
preloader = nil
-
24122
scope = strict_loading_value ? StrictLoadingScope : nil
-
24122
preload.each do |associations|
-
1507
preloader ||= build_preloader
-
1507
preloader.preload records, associations, scope
-
end
-
end
-
-
3
attr_reader :_deprecated_scope_source # :nodoc:
-
-
3
protected
-
3
attr_writer :_deprecated_scope_source # :nodoc:
-
-
3
def load_records(records)
-
609
@records = records.freeze
-
609
@loaded = true
-
end
-
-
3
def null_relation? # :nodoc:
-
535
is_a?(NullRelation)
-
end
-
-
3
private
-
3
def already_in_scope?
-
154054
@delegate_to_klass && begin
-
1599
scope = klass.current_scope(true)
-
1599
scope && !scope._deprecated_scope_source
-
end
-
end
-
-
3
def _deprecated_spawn(name)
-
2410
spawn.tap { |scope| scope._deprecated_scope_source = name }
-
end
-
-
3
def _deprecated_scope_block(name, &block)
-
425
-> record do
-
407
klass.current_scope = _deprecated_spawn(name)
-
407
yield record if block_given?
-
end
-
end
-
-
3
def _scoping(scope)
-
74069
previous, klass.current_scope = klass.current_scope(true), scope
-
74069
yield
-
ensure
-
74069
klass.current_scope = previous
-
end
-
-
3
def _substitute_values(values)
-
1266
values.map do |name, value|
-
1623
attr = table[name]
-
1623
unless Arel.arel_node?(value)
-
574
type = klass.type_for_attribute(attr.name)
-
574
value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
-
end
-
1623
[attr, value]
-
end
-
end
-
-
3
def _increment_attribute(attribute, value = 1)
-
1049
bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
-
1049
expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
-
1049
expr = value < 0 ? expr - bind : expr + bind
-
1049
expr.expr
-
end
-
-
3
def exec_queries(&block)
-
24169
skip_query_cache_if_necessary do
-
24169
records =
-
24169
if where_clause.contradiction?
-
32
[]
-
24137
elsif eager_loading?
-
547
apply_join_dependency do |relation, join_dependency|
-
535
if relation.null_relation?
-
9
[]
-
else
-
526
relation = join_dependency.apply_column_aliases(relation)
-
526
rows = connection.select_all(relation.arel, "SQL")
-
526
join_dependency.instantiate(rows, strict_loading_value, &block)
-
535
end.freeze
-
end
-
else
-
23590
klass.find_by_sql(arel, &block).freeze
-
end
-
-
24122
preload_associations(records) unless skip_preloading_value
-
-
24089
records.each(&:readonly!) if readonly_value
-
24089
records.each(&:strict_loading!) if strict_loading_value
-
-
24089
records
-
end
-
end
-
-
3
def skip_query_cache_if_necessary
-
31918
if skip_query_cache_value
-
1131
uncached do
-
1131
yield
-
end
-
else
-
30787
yield
-
end
-
end
-
-
3
def build_preloader
-
1324
ActiveRecord::Associations::Preloader.new
-
end
-
-
3
def references_eager_loaded_tables?
-
2922
joined_tables = arel.join_sources.map do |join|
-
678
if join.is_a?(Arel::Nodes::StringJoin)
-
30
tables_in_string(join.left)
-
else
-
648
[join.left.table_name, join.left.table_alias]
-
end
-
end
-
-
2922
joined_tables += [table.name, table.table_alias]
-
-
# always convert table names to downcase as in Oracle quoted table names are in uppercase
-
2922
joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
-
-
2922
(references_values - joined_tables).any?
-
end
-
-
3
def tables_in_string(string)
-
30
return [] if string.blank?
-
# always convert table names to downcase as in Oracle quoted table names are in uppercase
-
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
-
30
string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/relation/batches/batch_enumerator"
-
-
3
module ActiveRecord
-
3
module Batches
-
3
ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
-
-
# Looping through a collection of records from the database
-
# (using the Scoping::Named::ClassMethods.all method, for example)
-
# is very inefficient since it will try to instantiate all the objects at once.
-
#
-
# In that case, batch processing methods allow you to work
-
# with the records in batches, thereby greatly reducing memory consumption.
-
#
-
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
-
# specified by the +:batch_size+ option).
-
#
-
# Person.find_each do |person|
-
# person.do_awesome_stuff
-
# end
-
#
-
# Person.where("age > 21").find_each do |person|
-
# person.party_all_night!
-
# end
-
#
-
# If you do not provide a block to #find_each, it will return an Enumerator
-
# for chaining with other methods:
-
#
-
# Person.find_each.with_index do |person, index|
-
# person.award_trophy(index + 1)
-
# end
-
#
-
# ==== Options
-
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
-
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
-
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
-
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
-
# an order is present in the relation.
-
# * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
-
#
-
# Limits are honored, and if present there is no requirement for the batch
-
# size: it can be less than, equal to, or greater than the limit.
-
#
-
# The options +start+ and +finish+ are especially useful if you want
-
# multiple workers dealing with the same processing queue. You can make
-
# worker 1 handle all the records between id 1 and 9999 and worker 2
-
# handle from 10000 and beyond by setting the +:start+ and +:finish+
-
# option on each worker.
-
#
-
# # In worker 1, let's process until 9999 records.
-
# Person.find_each(finish: 9_999) do |person|
-
# person.party_all_night!
-
# end
-
#
-
# # In worker 2, let's process from record 10_000 and onwards.
-
# Person.find_each(start: 10_000) do |person|
-
# person.party_all_night!
-
# end
-
#
-
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
-
# ascending on the primary key ("id ASC").
-
# This also means that this method only works when the primary key is
-
# orderable (e.g. an integer or string).
-
#
-
# NOTE: By its nature, batch processing is subject to race conditions if
-
# other processes are modifying the database.
-
3
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
-
72
if block_given?
-
54
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
-
555
records.each { |record| yield record }
-
end
-
else
-
18
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
-
9
relation = self
-
9
apply_limits(relation, start, finish, order).size
-
end
-
end
-
end
-
-
# Yields each batch of records that was found by the find options as
-
# an array.
-
#
-
# Person.where("age > 21").find_in_batches do |group|
-
# sleep(50) # Make sure it doesn't get too crowded in there!
-
# group.each { |person| person.party_all_night! }
-
# end
-
#
-
# If you do not provide a block to #find_in_batches, it will return an Enumerator
-
# for chaining with other methods:
-
#
-
# Person.find_in_batches.with_index do |group, batch|
-
# puts "Processing group ##{batch}"
-
# group.each(&:recover_from_last_night!)
-
# end
-
#
-
# To be yielded each record one by one, use #find_each instead.
-
#
-
# ==== Options
-
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
-
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
-
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
-
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
-
# an order is present in the relation.
-
# * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
-
#
-
# Limits are honored, and if present there is no requirement for the batch
-
# size: it can be less than, equal to, or greater than the limit.
-
#
-
# The options +start+ and +finish+ are especially useful if you want
-
# multiple workers dealing with the same processing queue. You can make
-
# worker 1 handle all the records between id 1 and 9999 and worker 2
-
# handle from 10000 and beyond by setting the +:start+ and +:finish+
-
# option on each worker.
-
#
-
# # Let's process from record 10_000 on.
-
# Person.find_in_batches(start: 10_000) do |group|
-
# group.each { |person| person.party_all_night! }
-
# end
-
#
-
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
-
# ascending on the primary key ("id ASC").
-
# This also means that this method only works when the primary key is
-
# orderable (e.g. an integer or string).
-
#
-
# NOTE: By its nature, batch processing is subject to race conditions if
-
# other processes are modifying the database.
-
3
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
-
147
relation = self
-
147
unless block_given?
-
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
-
15
total = apply_limits(relation, start, finish, order).size
-
15
(total - 1).div(batch_size) + 1
-
21
end
-
end
-
-
126
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch|
-
387
yield batch.to_a
-
end
-
end
-
-
# Yields ActiveRecord::Relation objects to work with a batch of records.
-
#
-
# Person.where("age > 21").in_batches do |relation|
-
# relation.delete_all
-
# sleep(10) # Throttle the delete queries
-
# end
-
#
-
# If you do not provide a block to #in_batches, it will return a
-
# BatchEnumerator which is enumerable.
-
#
-
# Person.in_batches.each_with_index do |relation, batch_index|
-
# puts "Processing relation ##{batch_index}"
-
# relation.delete_all
-
# end
-
#
-
# Examples of calling methods on the returned BatchEnumerator object:
-
#
-
# Person.in_batches.delete_all
-
# Person.in_batches.update_all(awesome: true)
-
# Person.in_batches.each_record(&:party_all_night!)
-
#
-
# ==== Options
-
# * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
-
# * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
-
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
-
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
-
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
-
# an order is present in the relation.
-
# * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
-
#
-
# Limits are honored, and if present there is no requirement for the batch
-
# size, it can be less than, equal, or greater than the limit.
-
#
-
# The options +start+ and +finish+ are especially useful if you want
-
# multiple workers dealing with the same processing queue. You can make
-
# worker 1 handle all the records between id 1 and 9999 and worker 2
-
# handle from 10000 and beyond by setting the +:start+ and +:finish+
-
# option on each worker.
-
#
-
# # Let's process from record 10_000 on.
-
# Person.in_batches(start: 10_000).update_all(awesome: true)
-
#
-
# An example of calling where query method on the relation:
-
#
-
# Person.in_batches.each do |relation|
-
# relation.update_all('age = age + 1')
-
# relation.where('age > 21').update_all(should_party: true)
-
# relation.where('age <= 21').delete_all
-
# end
-
#
-
# NOTE: If you are going to iterate through each record, you should call
-
# #each_record on the yielded BatchEnumerator:
-
#
-
# Person.in_batches.each_record(&:party_all_night!)
-
#
-
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
-
# ascending on the primary key ("id ASC").
-
# This also means that this method only works when the primary key is
-
# orderable (e.g. an integer or string).
-
#
-
# NOTE: By its nature, batch processing is subject to race conditions if
-
# other processes are modifying the database.
-
3
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
-
273
relation = self
-
273
unless block_given?
-
30
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
-
end
-
-
243
unless [:asc, :desc].include?(order)
-
3
raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
-
end
-
-
240
if arel.orders.present?
-
30
act_on_ignored_order(error_on_ignore)
-
end
-
-
234
batch_limit = of
-
234
if limit_value
-
42
remaining = limit_value
-
42
batch_limit = remaining if remaining < batch_limit
-
end
-
-
234
relation = relation.reorder(batch_order(order)).limit(batch_limit)
-
234
relation = apply_limits(relation, start, finish, order)
-
234
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
-
234
batch_relation = relation
-
-
234
loop do
-
1083
if load
-
609
records = batch_relation.records
-
609
ids = records.map(&:id)
-
609
yielded_relation = where(primary_key => ids)
-
609
yielded_relation.load_records(records)
-
else
-
474
ids = batch_relation.pluck(primary_key)
-
474
yielded_relation = where(primary_key => ids)
-
end
-
-
1083
break if ids.empty?
-
-
990
primary_key_offset = ids.last
-
990
raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
-
-
987
yield yielded_relation
-
-
978
break if ids.length < batch_limit
-
-
873
if limit_value
-
102
remaining -= ids.length
-
-
102
if remaining == 0
-
# Saves a useless iteration when the limit is a multiple of the
-
# batch size.
-
24
break
-
78
elsif remaining < batch_limit
-
6
relation = relation.limit(remaining)
-
end
-
end
-
-
849
batch_relation = relation.where(
-
849
predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
-
)
-
end
-
end
-
-
3
private
-
3
def apply_limits(relation, start, finish, order)
-
258
relation = apply_start_limit(relation, start, order) if start
-
258
relation = apply_finish_limit(relation, finish, order) if finish
-
258
relation
-
end
-
-
3
def apply_start_limit(relation, start, order)
-
27
relation.where(predicate_builder[primary_key, start, order == :desc ? :lteq : :gteq])
-
end
-
-
3
def apply_finish_limit(relation, finish, order)
-
15
relation.where(predicate_builder[primary_key, finish, order == :desc ? :gteq : :lteq])
-
end
-
-
3
def batch_order(order)
-
234
table[primary_key].public_send(order)
-
end
-
-
3
def act_on_ignored_order(error_on_ignore)
-
30
raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
-
-
30
if raise_error
-
6
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
-
24
elsif logger
-
21
logger.warn(ORDER_IGNORE_MESSAGE)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Batches
-
3
class BatchEnumerator
-
3
include Enumerable
-
-
3
def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
-
30
@of = of
-
30
@relation = relation
-
30
@start = start
-
30
@finish = finish
-
end
-
-
# Looping through a collection of records from the database (using the
-
# +all+ method, for example) is very inefficient since it will try to
-
# instantiate all the objects at once.
-
#
-
# In that case, batch processing methods allow you to work with the
-
# records in batches, thereby greatly reducing memory consumption.
-
#
-
# Person.in_batches.each_record do |person|
-
# person.do_awesome_stuff
-
# end
-
#
-
# Person.where("age > 21").in_batches(of: 10).each_record do |person|
-
# person.party_all_night!
-
# end
-
#
-
# If you do not provide a block to #each_record, it will return an Enumerator
-
# for chaining with other methods:
-
#
-
# Person.in_batches.each_record.with_index do |person, index|
-
# person.award_trophy(index + 1)
-
# end
-
3
def each_record
-
15
return to_enum(:each_record) unless block_given?
-
-
9
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
-
153
relation.records.each { |record| yield record }
-
end
-
end
-
-
# Delegates #delete_all, #update_all, #destroy_all methods to each batch.
-
#
-
# People.in_batches.delete_all
-
# People.where('age < 10').in_batches.destroy_all
-
# People.in_batches.update_all('age = age + 1')
-
3
[:delete_all, :update_all, :destroy_all].each do |method|
-
9
define_method(method) do |*args, &block|
-
6
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
-
33
relation.send(method, *args, &block)
-
end
-
end
-
end
-
-
# Yields an ActiveRecord::Relation object for each batch of records.
-
#
-
# Person.in_batches.each do |relation|
-
# relation.update_all(awesome: true)
-
# end
-
3
def each
-
12
enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
-
60
return enum.each { |relation| yield relation } if block_given?
-
enum
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
-
3
module ActiveRecord
-
3
module Calculations
-
# Count the records.
-
#
-
# Person.count
-
# # => the total count of all people
-
#
-
# Person.count(:age)
-
# # => returns the total count of all people whose age is present in database
-
#
-
# Person.count(:all)
-
# # => performs a COUNT(*) (:all is an alias for '*')
-
#
-
# Person.distinct.count(:age)
-
# # => counts the number of different age values
-
#
-
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
-
# it returns a Hash whose keys represent the aggregated column,
-
# and the values are the respective amounts:
-
#
-
# Person.group(:city).count
-
# # => { 'Rome' => 5, 'Paris' => 3 }
-
#
-
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
-
# keys are an array containing the individual values of each column and the value
-
# of each key would be the #count.
-
#
-
# Article.group(:status, :category).count
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
-
# ["published", "business"]=>0, ["published", "technology"]=>2}
-
#
-
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
-
#
-
# Person.select(:age).count
-
# # => counts the number of different age values
-
#
-
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
-
# between databases. In invalid cases, an error from the database is thrown.
-
3
def count(column_name = nil)
-
3966
if block_given?
-
9
unless column_name.nil?
-
3
raise ArgumentError, "Column name argument is not supported when a block is passed."
-
end
-
-
6
super()
-
else
-
3957
calculate(:count, column_name)
-
end
-
end
-
-
# Calculates the average value on a given column. Returns +nil+ if there's
-
# no row. See #calculate for examples with options.
-
#
-
# Person.average(:age) # => 35.8
-
3
def average(column_name)
-
45
calculate(:average, column_name)
-
end
-
-
# Calculates the minimum value on a given column. The value is returned
-
# with the same data type of the column, or +nil+ if there's no row. See
-
# #calculate for examples with options.
-
#
-
# Person.minimum(:age) # => 7
-
3
def minimum(column_name)
-
78
calculate(:minimum, column_name)
-
end
-
-
# Calculates the maximum value on a given column. The value is returned
-
# with the same data type of the column, or +nil+ if there's no row. See
-
# #calculate for examples with options.
-
#
-
# Person.maximum(:age) # => 93
-
3
def maximum(column_name)
-
100
calculate(:maximum, column_name)
-
end
-
-
# Calculates the sum of values on a given column. The value is returned
-
# with the same data type of the column, +0+ if there's no row. See
-
# #calculate for examples with options.
-
#
-
# Person.sum(:age) # => 4562
-
3
def sum(column_name = nil)
-
157
if block_given?
-
6
unless column_name.nil?
-
3
raise ArgumentError, "Column name argument is not supported when a block is passed."
-
end
-
-
3
super()
-
else
-
151
calculate(:sum, column_name)
-
end
-
end
-
-
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
-
# #minimum, and #maximum have been added as shortcuts.
-
#
-
# Person.calculate(:count, :all) # The same as Person.count
-
# Person.average(:age) # SELECT AVG(age) FROM people...
-
#
-
# # Selects the minimum age for any family without any minors
-
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
-
#
-
# Person.sum("2 * age")
-
#
-
# There are two basic forms of output:
-
#
-
# * Single aggregate value: The single value is type cast to Integer for COUNT, Float
-
# for AVG, and the given column's type for everything else.
-
#
-
# * Grouped values: This returns an ordered hash of the values and groups them. It
-
# takes either a column name, or the name of a belongs_to association.
-
#
-
# values = Person.group('last_name').maximum(:age)
-
# puts values["Drake"]
-
# # => 43
-
#
-
# drake = Family.find_by(last_name: 'Drake')
-
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
-
# puts values[drake]
-
# # => 43
-
#
-
# values.each do |family, max_age|
-
# ...
-
# end
-
3
def calculate(operation, column_name)
-
4436
if has_include?(column_name)
-
126
relation = apply_join_dependency
-
-
123
if operation.to_s.downcase == "count"
-
108
unless distinct_value || distinct_select?(column_name || select_for_count)
-
93
relation.distinct!
-
93
relation.select_values = [ klass.primary_key || table[Arel.star] ]
-
end
-
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
-
108
relation.order_values = [] if group_values.empty?
-
end
-
-
123
relation.calculate(operation, column_name)
-
else
-
4310
perform_calculation(operation, column_name)
-
end
-
end
-
-
# Use #pluck as a shortcut to select one or more attributes without
-
# loading a bunch of records just to grab the attributes you want.
-
#
-
# Person.pluck(:name)
-
#
-
# instead of
-
#
-
# Person.all.map(&:name)
-
#
-
# Pluck returns an Array of attribute values type-casted to match
-
# the plucked column names, if they can be deduced. Plucking an SQL fragment
-
# returns String values by default.
-
#
-
# Person.pluck(:name)
-
# # SELECT people.name FROM people
-
# # => ['David', 'Jeremy', 'Jose']
-
#
-
# Person.pluck(:id, :name)
-
# # SELECT people.id, people.name FROM people
-
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
-
#
-
# Person.distinct.pluck(:role)
-
# # SELECT DISTINCT role FROM people
-
# # => ['admin', 'member', 'guest']
-
#
-
# Person.where(age: 21).limit(5).pluck(:id)
-
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
-
# # => [2, 3]
-
#
-
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
-
# # SELECT DATEDIFF(updated_at, created_at) FROM people
-
# # => ['0', '27761', '173']
-
#
-
# See also #ids.
-
#
-
3
def pluck(*column_names)
-
2003
if loaded? && all_attributes?(column_names)
-
18
return records.pluck(*column_names)
-
end
-
-
1985
if has_include?(column_names.first)
-
57
relation = apply_join_dependency
-
57
relation.pluck(*column_names)
-
else
-
1928
klass.disallow_raw_sql!(column_names)
-
1919
columns = arel_columns(column_names)
-
1919
relation = spawn
-
1919
relation.select_values = columns
-
1919
result = skip_query_cache_if_necessary do
-
1919
if where_clause.contradiction?
-
9
ActiveRecord::Result.new([], [])
-
else
-
1910
klass.connection.select_all(relation.arel, nil)
-
end
-
end
-
1919
type_cast_pluck_values(result, columns)
-
end
-
end
-
-
# Pick the value(s) from the named column(s) in the current relation.
-
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
-
# when you have a relation that's already narrowed down to a single row.
-
#
-
# Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
-
# more efficient. The value is, again like with pluck, typecast by the column type.
-
#
-
# Person.where(id: 1).pick(:name)
-
# # SELECT people.name FROM people WHERE id = 1 LIMIT 1
-
# # => 'David'
-
#
-
# Person.where(id: 1).pick(:name, :email_address)
-
# # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
-
# # => [ 'David', 'david@loudthinking.com' ]
-
3
def pick(*column_names)
-
42
if loaded? && all_attributes?(column_names)
-
15
return records.pick(*column_names)
-
end
-
-
27
limit(1).pluck(*column_names).first
-
end
-
-
# Pluck all the ID's for the relation using the table's primary key
-
#
-
# Person.ids # SELECT people.id FROM people
-
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
-
3
def ids
-
18
pluck primary_key
-
end
-
-
3
private
-
3
def all_attributes?(column_names)
-
39
(column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
-
end
-
-
3
def has_include?(column_name)
-
6421
eager_loading? || (includes_values.present? && column_name && column_name != :all)
-
end
-
-
3
def perform_calculation(operation, column_name)
-
4310
operation = operation.to_s.downcase
-
-
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
-
# considered distinct.
-
4310
distinct = distinct_value
-
-
4310
if operation == "count"
-
3942
column_name ||= select_for_count
-
3942
if column_name == :all
-
3480
if !distinct
-
3357
distinct = distinct_select?(select_for_count) if group_values.empty?
-
123
elsif group_values.any? || select_values.empty? && order_values.empty?
-
45
column_name = primary_key
-
end
-
462
elsif distinct_select?(column_name)
-
18
distinct = nil
-
end
-
end
-
-
4310
if group_values.any?
-
259
execute_grouped_calculation(operation, column_name, distinct)
-
else
-
4051
execute_simple_calculation(operation, column_name, distinct)
-
end
-
end
-
-
3
def distinct_select?(column_name)
-
3848
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
-
end
-
-
3
def aggregate_column(column_name)
-
4055
return column_name if Arel::Expressions === column_name
-
-
4010
arel_column(column_name.to_s) do |name|
-
3255
Arel.sql(column_name == :all ? "*" : name)
-
end
-
end
-
-
3
def operation_over_aggregate_column(column, operation, distinct)
-
4304
operation == "count" ? column.count(distinct) : column.send(operation)
-
end
-
-
3
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
-
4051
if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
-
# Shortcut when limit is zero.
-
267
return 0 if limit_value == 0
-
-
261
query_builder = build_count_subquery(spawn, column_name, distinct)
-
else
-
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
-
3784
relation = unscope(:order).distinct!(false)
-
-
3784
column = aggregate_column(column_name)
-
3784
select_value = operation_over_aggregate_column(column, operation, distinct)
-
3784
select_value.distinct = true if operation == "sum" && distinct
-
-
3784
relation.select_values = [select_value]
-
-
3784
query_builder = relation.arel
-
end
-
-
8090
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
-
-
4042
type_cast_calculated_value(result.cast_values.first, operation) do |value|
-
194
type = column.try(:type_caster) ||
-
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
-
194
type.deserialize(value)
-
end
-
end
-
-
3
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
-
259
group_fields = group_values
-
259
group_fields = group_fields.uniq if group_fields.size > 1
-
-
259
unless group_fields == group_values
-
6
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`#{operation}` with group by duplicated fields does no longer affect to result in Rails 6.2.
-
To migrate to Rails 6.2's behavior, use `uniq!(:group)` to deduplicate group fields
-
(`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`).
-
MSG
-
6
group_fields = group_values
-
end
-
-
259
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
-
241
association = klass._reflect_on_association(group_fields.first)
-
241
associated = association && association.belongs_to? # only count belongs_to associations
-
241
group_fields = Array(association.foreign_key) if associated
-
end
-
259
group_fields = arel_columns(group_fields)
-
-
259
group_aliases = group_fields.map { |field|
-
277
field = connection.visitor.compile(field) if Arel.arel_node?(field)
-
277
column_alias_for(field.to_s.downcase)
-
}
-
259
group_columns = group_aliases.zip(group_fields)
-
-
259
column = aggregate_column(column_name)
-
259
column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
-
259
select_value = operation_over_aggregate_column(column, operation, distinct)
-
259
select_value.as(column_alias)
-
-
259
select_values = [select_value]
-
259
select_values += self.select_values unless having_clause.empty?
-
-
259
select_values.concat group_columns.map { |aliaz, field|
-
277
if field.respond_to?(:as)
-
237
field.as(aliaz)
-
else
-
40
"#{field} AS #{aliaz}"
-
end
-
}
-
-
259
relation = except(:group).distinct!(false)
-
259
relation.group_values = group_fields
-
259
relation.select_values = select_values
-
-
518
calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
-
-
259
if association
-
69
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
-
15
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
-
15
key_records = key_records.index_by(&:id)
-
end
-
-
259
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
-
277
types[aliaz] = type_for(col_name) do
-
37
calculated_data.column_types.fetch(aliaz, Type.default_value)
-
end
-
end
-
-
259
hash_rows = calculated_data.cast_values(key_types).map! do |row|
-
889
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
-
1875
hash[col_name] = row[i]
-
end
-
end
-
-
259
type = nil
-
259
hash_rows.each_with_object({}) do |row, result|
-
1871
key = group_aliases.map { |aliaz| row[aliaz] }
-
889
key = key.first if key.size == 1
-
889
key = key_records[key] if associated
-
-
889
result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
-
420
type ||= column.try(:type_caster) ||
-
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
-
420
type.deserialize(value)
-
end
-
end
-
end
-
-
# Converts the given field to the value that the database adapter returns as
-
# a usable column name:
-
#
-
# column_alias_for("users.id") # => "users_id"
-
# column_alias_for("sum(id)") # => "sum_id"
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
-
# column_alias_for("count(*)") # => "count_all"
-
3
def column_alias_for(field)
-
536
column_alias = +field
-
536
column_alias.gsub!(/\*/, "all")
-
536
column_alias.gsub!(/\W+/, " ")
-
536
column_alias.strip!
-
536
column_alias.gsub!(/ +/, "_")
-
-
536
connection.table_alias_for(column_alias)
-
end
-
-
3
def type_for(field, &block)
-
277
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
-
277
@klass.type_for_attribute(field_name, &block)
-
end
-
-
3
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
-
87
each_join_dependencies(join_dependencies) do |join|
-
201
type = join.base_klass.attribute_types.fetch(name, nil)
-
201
return type if type
-
end
-
nil
-
end
-
-
3
def type_cast_pluck_values(result, columns)
-
1919
cast_types = if result.columns.size != columns.size
-
15
klass.attribute_types
-
else
-
1904
join_dependencies = nil
-
1904
columns.map.with_index do |column, i|
-
1974
column.try(:type_caster) ||
-
klass.attribute_types.fetch(name = result.columns[i]) do
-
45
join_dependencies ||= build_join_dependencies
-
45
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
-
result.column_types[name] || Type.default_value
-
end
-
end
-
end
-
1919
result.cast_values(cast_types)
-
end
-
-
3
def type_cast_calculated_value(value, operation)
-
4931
case operation
-
when "count"
-
4260
value.to_i
-
when "sum"
-
375
yield value || 0
-
when "average"
-
57
value&.respond_to?(:to_d) ? value.to_d : value
-
else # "minimum", "maximum"
-
239
yield value
-
end
-
end
-
-
3
def select_for_count
-
6346
if select_values.present?
-
160
return select_values.first if select_values.one?
-
9
select_values.join(", ")
-
else
-
6186
:all
-
end
-
end
-
-
3
def build_count_subquery(relation, column_name, distinct)
-
261
if column_name == :all
-
249
column_alias = Arel.star
-
249
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
-
else
-
12
column_alias = Arel.sql("count_column")
-
12
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
-
end
-
-
261
subquery_alias = Arel.sql("subquery_for_count")
-
261
select_value = operation_over_aggregate_column(column_alias, "count", false)
-
-
261
relation.build_subquery(subquery_alias, select_value)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "mutex_m"
-
3
require "active_support/core_ext/module/delegation"
-
-
3
module ActiveRecord
-
3
module Delegation # :nodoc:
-
3
module DelegateCache # :nodoc:
-
3
def relation_delegate_class(klass)
-
125087
@relation_delegate_cache[klass]
-
end
-
-
3
def initialize_relation_delegate_cache
-
2946
@relation_delegate_cache = cache = {}
-
[
-
ActiveRecord::Relation,
-
ActiveRecord::Associations::CollectionProxy,
-
ActiveRecord::AssociationRelation
-
2946
].each do |klass|
-
8838
delegate = Class.new(klass) {
-
8838
include ClassSpecificRelation
-
}
-
8838
include_relation_methods(delegate)
-
8838
mangled_name = klass.name.gsub("::", "_")
-
8838
const_set mangled_name, delegate
-
8838
private_constant mangled_name
-
-
8838
cache[klass] = delegate
-
end
-
end
-
-
3
def inherited(child_class)
-
2946
child_class.initialize_relation_delegate_cache
-
2946
super
-
end
-
-
3
def generate_relation_method(method)
-
3548
generated_relation_methods.generate_method(method)
-
end
-
-
3
protected
-
3
def include_relation_methods(delegate)
-
10929
superclass.include_relation_methods(delegate) unless base_class?
-
10929
delegate.include generated_relation_methods
-
end
-
-
3
private
-
3
def generated_relation_methods
-
14480
@generated_relation_methods ||= GeneratedRelationMethods.new.tap do |mod|
-
2946
const_set(:GeneratedRelationMethods, mod)
-
2946
private_constant :GeneratedRelationMethods
-
end
-
end
-
end
-
-
3
class GeneratedRelationMethods < Module # :nodoc:
-
3
include Mutex_m
-
-
3
def generate_method(method)
-
3548
synchronize do
-
3548
return if method_defined?(method)
-
-
3533
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
-
3524
definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block"
-
3524
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method}(#{definition})
-
scoping { klass.#{method}(#{definition}) }
-
end
-
RUBY
-
else
-
9
define_method(method) do |*args, &block|
-
6
scoping { klass.public_send(method, *args, &block) }
-
end
-
9
ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
-
end
-
end
-
end
-
end
-
3
private_constant :GeneratedRelationMethods
-
-
3
extend ActiveSupport::Concern
-
-
# This module creates compiled delegation methods dynamically at runtime, which makes
-
# subsequent calls to that method faster by avoiding method_missing. The delegations
-
# may vary depending on the klass of a relation, so we create a subclass of Relation
-
# for each different klass, and the delegations are compiled into that subclass only.
-
-
3
delegate :to_xml, :encode_with, :length, :each, :join,
-
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
-
:to_sentence, :to_formatted_s, :as_json,
-
:shuffle, :split, :slice, :index, :rindex, to: :records
-
-
3
delegate :primary_key, :connection, to: :klass
-
-
3
module ClassSpecificRelation # :nodoc:
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods # :nodoc:
-
3
def name
-
24
superclass.name
-
end
-
end
-
-
3
private
-
3
def method_missing(method, *args, &block)
-
2301
if @klass.respond_to?(method)
-
2301
@klass.generate_relation_method(method)
-
4602
scoping { @klass.public_send(method, *args, &block) }
-
else
-
super
-
end
-
end
-
3
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
-
end
-
-
3
module ClassMethods # :nodoc:
-
3
def create(klass, *args, **kwargs)
-
125087
relation_class_for(klass).new(klass, *args, **kwargs)
-
end
-
-
3
private
-
3
def relation_class_for(klass)
-
125087
klass.relation_delegate_class(self)
-
end
-
end
-
-
3
private
-
3
def respond_to_missing?(method, _)
-
303
super || @klass.respond_to?(method)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/string/filters"
-
-
3
module ActiveRecord
-
3
module FinderMethods
-
3
ONE_AS_ONE = "1 AS one"
-
-
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
-
# If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.
-
# If the primary key is an integer, find by id coerces its arguments by using +to_i+.
-
#
-
# Person.find(1) # returns the object for ID = 1
-
# Person.find("1") # returns the object for ID = 1
-
# Person.find("31-sarah") # returns the object for ID = 31
-
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
-
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
-
# Person.find([1]) # returns an array for the object with ID = 1
-
# Person.where("administrator = 1").order("created_on DESC").find(1)
-
#
-
# NOTE: The returned records are in the same order as the ids you provide.
-
# If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
-
# method and provide an explicit ActiveRecord::QueryMethods#order option.
-
# But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound.
-
#
-
# ==== Find with lock
-
#
-
# Example for find with a lock: Imagine two concurrent transactions:
-
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
-
# in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
-
# transaction has to wait until the first is finished; we get the
-
# expected <tt>person.visits == 4</tt>.
-
#
-
# Person.transaction do
-
# person = Person.lock(true).find(1)
-
# person.visits += 1
-
# person.save!
-
# end
-
#
-
# ==== Variations of #find
-
#
-
# Person.where(name: 'Spartacus', rating: 4)
-
# # returns a chainable list (which can be empty).
-
#
-
# Person.find_by(name: 'Spartacus', rating: 4)
-
# # returns the first item or nil.
-
#
-
# Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
-
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
-
#
-
# Person.find_or_create_by(name: 'Spartacus', rating: 4)
-
# # returns the first item or creates it and returns it.
-
#
-
# ==== Alternatives for #find
-
#
-
# Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
-
# # returns a boolean indicating if any record with the given conditions exist.
-
#
-
# Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
-
# # returns a chainable list of instances with only the mentioned fields.
-
#
-
# Person.where(name: 'Spartacus', rating: 4).ids
-
# # returns an Array of ids.
-
#
-
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
-
# # returns an Array of the required fields.
-
3
def find(*args)
-
12159
return super if block_given?
-
12129
find_with_ids(*args)
-
end
-
-
# Finds the first record matching the specified conditions. There
-
# is no implied ordering so if order matters, you should specify it
-
# yourself.
-
#
-
# If no record is found, returns <tt>nil</tt>.
-
#
-
# Post.find_by name: 'Spartacus', rating: 4
-
# Post.find_by "published_at < ?", 2.weeks.ago
-
3
def find_by(arg, *args)
-
485
where(arg, *args).take
-
end
-
-
# Like #find_by, except that if no record is found, raises
-
# an ActiveRecord::RecordNotFound error.
-
3
def find_by!(arg, *args)
-
39
where(arg, *args).take!
-
end
-
-
# Gives a record (or N records if a parameter is supplied) without any implied
-
# order. The order will depend on the database implementation.
-
# If an order is supplied it will be respected.
-
#
-
# Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
-
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
-
# Person.where(["name LIKE '%?'", name]).take
-
3
def take(limit = nil)
-
12350
limit ? find_take_with_limit(limit) : find_take
-
end
-
-
# Same as #take but raises ActiveRecord::RecordNotFound if no record
-
# is found. Note that #take! accepts no arguments.
-
3
def take!
-
63
take || raise_record_not_found_exception!
-
end
-
-
# Find the first record (or first N records if a parameter is supplied).
-
# If no order is defined it will order by primary key.
-
#
-
# Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
-
# Person.where(["user_name = ?", user_name]).first
-
# Person.where(["user_name = :u", { u: user_name }]).first
-
# Person.order("created_on DESC").offset(5).first
-
# Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
-
#
-
3
def first(limit = nil)
-
3795
check_reorder_deprecation unless loaded?
-
-
3795
if limit
-
66
find_nth_with_limit(0, limit)
-
else
-
3729
find_nth 0
-
end
-
end
-
-
# Same as #first but raises ActiveRecord::RecordNotFound if no record
-
# is found. Note that #first! accepts no arguments.
-
3
def first!
-
57
first || raise_record_not_found_exception!
-
end
-
-
# Find the last record (or last N records if a parameter is supplied).
-
# If no order is defined it will order by primary key.
-
#
-
# Person.last # returns the last object fetched by SELECT * FROM people
-
# Person.where(["user_name = ?", user_name]).last
-
# Person.order("created_on DESC").offset(5).last
-
# Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
-
#
-
# Take note that in that last case, the results are sorted in ascending order:
-
#
-
# [#<Person id:2>, #<Person id:3>, #<Person id:4>]
-
#
-
# and not:
-
#
-
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
-
3
def last(limit = nil)
-
423
return find_last(limit) if loaded? || has_limit_or_offset?
-
-
291
result = ordered_relation.limit(limit)
-
291
result = result.reverse_order!
-
-
288
limit ? result.reverse : result.first
-
end
-
-
# Same as #last but raises ActiveRecord::RecordNotFound if no record
-
# is found. Note that #last! accepts no arguments.
-
3
def last!
-
24
last || raise_record_not_found_exception!
-
end
-
-
# Find the second record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.second # returns the second object fetched by SELECT * FROM people
-
# Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
-
# Person.where(["user_name = :u", { u: user_name }]).second
-
3
def second
-
56
find_nth 1
-
end
-
-
# Same as #second but raises ActiveRecord::RecordNotFound if no record
-
# is found.
-
3
def second!
-
6
second || raise_record_not_found_exception!
-
end
-
-
# Find the third record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.third # returns the third object fetched by SELECT * FROM people
-
# Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
-
# Person.where(["user_name = :u", { u: user_name }]).third
-
3
def third
-
51
find_nth 2
-
end
-
-
# Same as #third but raises ActiveRecord::RecordNotFound if no record
-
# is found.
-
3
def third!
-
9
third || raise_record_not_found_exception!
-
end
-
-
# Find the fourth record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.fourth # returns the fourth object fetched by SELECT * FROM people
-
# Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
-
# Person.where(["user_name = :u", { u: user_name }]).fourth
-
3
def fourth
-
30
find_nth 3
-
end
-
-
# Same as #fourth but raises ActiveRecord::RecordNotFound if no record
-
# is found.
-
3
def fourth!
-
9
fourth || raise_record_not_found_exception!
-
end
-
-
# Find the fifth record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.fifth # returns the fifth object fetched by SELECT * FROM people
-
# Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
-
# Person.where(["user_name = :u", { u: user_name }]).fifth
-
3
def fifth
-
30
find_nth 4
-
end
-
-
# Same as #fifth but raises ActiveRecord::RecordNotFound if no record
-
# is found.
-
3
def fifth!
-
9
fifth || raise_record_not_found_exception!
-
end
-
-
# Find the forty-second record. Also known as accessing "the reddit".
-
# If no order is defined it will order by primary key.
-
#
-
# Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
-
# Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
-
# Person.where(["user_name = :u", { u: user_name }]).forty_two
-
3
def forty_two
-
3
find_nth 41
-
end
-
-
# Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
-
# is found.
-
3
def forty_two!
-
forty_two || raise_record_not_found_exception!
-
end
-
-
# Find the third-to-last record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
-
# Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
-
# Person.where(["user_name = :u", { u: user_name }]).third_to_last
-
3
def third_to_last
-
42
find_nth_from_last 3
-
end
-
-
# Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
-
# is found.
-
3
def third_to_last!
-
9
third_to_last || raise_record_not_found_exception!
-
end
-
-
# Find the second-to-last record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
-
# Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
-
# Person.where(["user_name = :u", { u: user_name }]).second_to_last
-
3
def second_to_last
-
39
find_nth_from_last 2
-
end
-
-
# Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
-
# is found.
-
3
def second_to_last!
-
9
second_to_last || raise_record_not_found_exception!
-
end
-
-
# Returns true if a record exists in the table that matches the +id+ or
-
# conditions given, or false otherwise. The argument can take six forms:
-
#
-
# * Integer - Finds the record with this primary key.
-
# * String - Finds the record with a primary key corresponding to this
-
# string (such as <tt>'5'</tt>).
-
# * Array - Finds the record that matches these +where+-style conditions
-
# (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
-
# * Hash - Finds the record that matches these +where+-style conditions
-
# (such as <tt>{name: 'David'}</tt>).
-
# * +false+ - Returns always +false+.
-
# * No args - Returns +false+ if the relation is empty, +true+ otherwise.
-
#
-
# For more information about specifying conditions as a hash or array,
-
# see the Conditions section in the introduction to ActiveRecord::Base.
-
#
-
# Note: You can't pass in a condition as a string (like <tt>name =
-
# 'Jamie'</tt>), since it would be sanitized and then queried against
-
# the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
-
#
-
# Person.exists?(5)
-
# Person.exists?('5')
-
# Person.exists?(['name LIKE ?', "%#{query}%"])
-
# Person.exists?(id: [1, 4, 8])
-
# Person.exists?(name: 'David')
-
# Person.exists?(false)
-
# Person.exists?
-
# Person.where(name: 'Spartacus', rating: 4).exists?
-
3
def exists?(conditions = :none)
-
1357
if Base === conditions
-
3
raise ArgumentError, <<-MSG.squish
-
You are passing an instance of ActiveRecord::Base to `exists?`.
-
Please pass the id of the object by calling `.id`.
-
MSG
-
end
-
-
1354
return false if !conditions || limit_value == 0
-
-
1324
if eager_loading?
-
42
relation = apply_join_dependency(eager_loading: false)
-
39
return relation.exists?(conditions)
-
end
-
-
1282
relation = construct_relation_for_exists(conditions)
-
-
2552
skip_query_cache_if_necessary { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 }
-
end
-
-
# This method is called whenever no records are found with either a single
-
# id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
-
#
-
# The error message is different depending on whether a single id or
-
# multiple ids are provided. If multiple ids are provided, then the number
-
# of results obtained should be provided in the +result_size+ argument and
-
# the expected number of results should be provided in the +expected_size+
-
# argument.
-
3
def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
-
166
conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
-
-
166
name = @klass.name
-
-
166
if ids.nil?
-
51
error = +"Couldn't find #{name}"
-
51
error << " with#{conditions}" if conditions
-
51
raise RecordNotFound.new(error, name, key)
-
115
elsif Array(ids).size == 1
-
88
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
-
88
raise RecordNotFound.new(error, name, key, ids)
-
else
-
27
error = +"Couldn't find all #{name.pluralize} with '#{key}': "
-
27
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
-
27
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
-
27
raise RecordNotFound.new(error, name, key, ids)
-
end
-
end
-
-
3
private
-
3
def check_reorder_deprecation
-
3438
if !order_values.empty? && order_values.all?(&:blank?)
-
3
blank_value = order_values.first
-
3
ActiveSupport::Deprecation.warn(<<~MSG.squish)
-
`.reorder(#{blank_value.inspect})` with `.first` / `.first!` no longer
-
takes non-deterministic result in Rails 6.2.
-
To continue taking non-deterministic result, use `.take` / `.take!` instead.
-
MSG
-
end
-
end
-
-
3
def construct_relation_for_exists(conditions)
-
1282
conditions = sanitize_forbidden_attributes(conditions)
-
-
1279
if distinct_value && offset_value
-
18
relation = except(:order).limit!(1)
-
else
-
1261
relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
-
end
-
-
1279
case conditions
-
when Array, Hash
-
87
relation.where!(conditions) unless conditions.empty?
-
else
-
1192
relation.where!(primary_key => conditions) unless conditions == :none
-
end
-
-
1276
relation
-
end
-
-
3
def apply_join_dependency(eager_loading: group_values.empty?)
-
802
join_dependency = construct_join_dependency(
-
eager_load_values | includes_values, Arel::Nodes::OuterJoin
-
)
-
784
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
-
-
784
if eager_loading && !(
-
733
using_limitable_reflections?(join_dependency.reflections) &&
-
using_limitable_reflections?(
-
construct_join_dependency(
-
select_association_list(joins_values).concat(
-
select_association_list(left_outer_joins_values)
-
), nil
-
).reflections
-
)
-
)
-
520
if has_limit_or_offset?
-
250
limited_ids = limited_ids_for(relation)
-
250
limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
-
end
-
520
relation.limit_value = relation.offset_value = nil
-
end
-
-
784
if block_given?
-
544
yield relation, join_dependency
-
else
-
240
relation
-
end
-
end
-
-
3
def limited_ids_for(relation)
-
250
values = @klass.connection.columns_for_distinct(
-
connection.visitor.compile(table[primary_key]),
-
relation.order_values
-
)
-
-
250
relation = relation.except(:select).select(values).distinct!
-
-
500
id_rows = skip_query_cache_if_necessary { @klass.connection.select_rows(relation.arel, "SQL") }
-
250
id_rows.map(&:last)
-
end
-
-
3
def using_limitable_reflections?(reflections)
-
967
reflections.none?(&:collection?)
-
end
-
-
3
def find_with_ids(*ids)
-
12129
raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
-
-
12126
expects_array = ids.first.kind_of?(Array)
-
12126
return [] if expects_array && ids.first.empty?
-
-
12120
ids = ids.flatten.compact.uniq
-
-
12120
model_name = @klass.name
-
-
12120
case ids.size
-
when 0
-
9
error_message = "Couldn't find #{model_name} without an ID"
-
9
raise RecordNotFound.new(error_message, model_name, primary_key)
-
when 1
-
11693
result = find_one(ids.first)
-
11585
expects_array ? [ result ] : result
-
else
-
418
find_some(ids)
-
end
-
end
-
-
3
def find_one(id)
-
11693
if ActiveRecord::Base === id
-
3
raise ArgumentError, <<-MSG.squish
-
You are passing an instance of ActiveRecord::Base to `find`.
-
Please pass the id of the object by calling `.id`.
-
MSG
-
end
-
-
11690
relation = where(primary_key => id)
-
11690
record = relation.take
-
-
11673
raise_record_not_found_exception!(id, 0, 1) unless record
-
-
11585
record
-
end
-
-
3
def find_some(ids)
-
418
return find_some_ordered(ids) unless order_values.present?
-
-
27
result = where(primary_key => ids).to_a
-
-
27
expected_size =
-
27
if limit_value && ids.size > limit_value
-
3
limit_value
-
else
-
24
ids.size
-
end
-
-
# 11 ids with limit 3, offset 9 should give 2 results.
-
27
if offset_value && (ids.size - offset_value < expected_size)
-
expected_size = ids.size - offset_value
-
end
-
-
27
if result.size == expected_size
-
18
result
-
else
-
9
raise_record_not_found_exception!(ids, result.size, expected_size)
-
end
-
end
-
-
3
def find_some_ordered(ids)
-
391
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
-
-
391
result = except(:limit, :offset).where(primary_key => ids).records
-
-
391
if result.size == ids.size
-
379
pk_type = @klass.type_for_attribute(primary_key)
-
-
379
records_by_id = result.index_by(&:id)
-
1159
ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
-
else
-
12
raise_record_not_found_exception!(ids, result.size, ids.size)
-
end
-
end
-
-
3
def find_take
-
12311
if loaded?
-
12
records.first
-
else
-
12299
@take ||= limit(1).records.first
-
end
-
end
-
-
3
def find_take_with_limit(limit)
-
39
if loaded?
-
21
records.take(limit)
-
else
-
18
limit(limit).to_a
-
end
-
end
-
-
3
def find_nth(index)
-
3899
@offsets ||= {}
-
3899
@offsets[index] ||= find_nth_with_limit(index, 1).first
-
end
-
-
3
def find_nth_with_limit(index, limit)
-
3926
if loaded?
-
418
records[index, limit] || []
-
else
-
3508
relation = ordered_relation
-
-
3503
if limit_value
-
69
limit = [limit_value - index, limit].min
-
end
-
-
3503
if limit > 0
-
3494
relation = relation.offset((offset_value || 0) + index) unless index.zero?
-
3494
relation.limit(limit).to_a
-
else
-
9
[]
-
end
-
end
-
end
-
-
3
def find_nth_from_last(index)
-
81
if loaded?
-
18
records[-index]
-
else
-
63
relation = ordered_relation
-
-
63
if equal?(relation) || has_limit_or_offset?
-
39
relation.records[-index]
-
else
-
24
relation.last(index)[-index]
-
end
-
end
-
end
-
-
3
def find_last(limit)
-
132
limit ? records.last(limit) : records.last
-
end
-
-
3
def ordered_relation
-
3862
if order_values.empty? && (implicit_order_column || primary_key)
-
3112
if implicit_order_column && primary_key && implicit_order_column != primary_key
-
9
order(table[implicit_order_column].asc, table[primary_key].asc)
-
else
-
3103
order(table[implicit_order_column || primary_key].asc)
-
end
-
else
-
750
self
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class Relation
-
3
class FromClause # :nodoc:
-
3
attr_reader :value, :name
-
-
3
def initialize(value, name)
-
141
@value = value
-
141
@name = name
-
end
-
-
3
def merge(other)
-
self
-
end
-
-
3
def empty?
-
120565
value.nil?
-
end
-
-
3
def ==(other)
-
3
self.class == other.class && value == other.value && name == other.name
-
end
-
-
3
def self.empty
-
141613
@empty ||= new(nil, nil).freeze
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/hash/keys"
-
-
3
module ActiveRecord
-
3
class Relation
-
3
class HashMerger # :nodoc:
-
3
attr_reader :relation, :hash
-
-
3
def initialize(relation, hash, rewhere = nil)
-
756
hash.assert_valid_keys(*Relation::VALUE_METHODS)
-
-
753
@relation = relation
-
753
@hash = hash
-
753
@rewhere = rewhere
-
end
-
-
3
def merge
-
753
Merger.new(relation, other, @rewhere).merge
-
end
-
-
# Applying values to a relation has some side effects. E.g.
-
# interpolation might take place for where values. So we should
-
# build a relation to merge in rather than directly merging
-
# the values.
-
3
def other
-
753
other = Relation.create(
-
relation.klass,
-
table: relation.table,
-
predicate_builder: relation.predicate_builder
-
)
-
753
hash.each do |k, v|
-
1371
k = :_select if k == :select
-
1371
if Array === v
-
156
other.send("#{k}!", *v)
-
else
-
1215
other.send("#{k}!", v)
-
end
-
end
-
753
other
-
end
-
end
-
-
3
class Merger # :nodoc:
-
3
attr_reader :relation, :values, :other
-
-
3
def initialize(relation, other, rewhere = nil)
-
39613
@relation = relation
-
39613
@values = other.values
-
39613
@other = other
-
39613
@rewhere = rewhere
-
end
-
-
3
NORMAL_VALUES = Relation::VALUE_METHODS -
-
Relation::CLAUSE_METHODS -
-
[:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
-
-
3
def normal_values
-
39613
NORMAL_VALUES
-
end
-
-
3
def merge
-
39613
normal_values.each do |name|
-
554582
value = values[name]
-
# The unless clause is here mostly for performance reasons (since the `send` call might be moderately
-
# expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
-
# `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
-
# don't fall through the cracks.
-
554582
unless value.nil? || (value.blank? && false != value)
-
13463
if name == :select
-
188
relation._select!(*value)
-
else
-
13275
relation.send("#{name}!", *value)
-
end
-
end
-
end
-
-
39613
merge_multi_values
-
39613
merge_single_values
-
39613
merge_clauses
-
39610
merge_preloads
-
39610
merge_joins
-
39610
merge_outer_joins
-
-
39610
relation
-
end
-
-
3
private
-
3
def merge_preloads
-
39610
return if other.preload_values.empty? && other.includes_values.empty?
-
-
1272
if other.klass == relation.klass
-
1266
relation.preload_values |= other.preload_values unless other.preload_values.empty?
-
1266
relation.includes_values |= other.includes_values unless other.includes_values.empty?
-
else
-
6
reflection = relation.klass.reflect_on_all_associations.find do |r|
-
6
r.class_name == other.klass.name
-
end || return
-
-
6
unless other.preload_values.empty?
-
3
relation.preload! reflection.name => other.preload_values
-
end
-
-
6
unless other.includes_values.empty?
-
3
relation.includes! reflection.name => other.includes_values
-
end
-
end
-
end
-
-
3
def merge_joins
-
39610
return if other.joins_values.empty?
-
-
4854
if other.klass == relation.klass
-
4818
relation.joins_values |= other.joins_values
-
else
-
36
associations, others = other.joins_values.partition do |join|
-
39
case join
-
30
when Hash, Symbol, Array; true
-
end
-
end
-
-
36
join_dependency = other.construct_join_dependency(
-
associations, Arel::Nodes::InnerJoin
-
)
-
36
relation.joins!(join_dependency, *others)
-
end
-
end
-
-
3
def merge_outer_joins
-
39610
return if other.left_outer_joins_values.empty?
-
-
57
if other.klass == relation.klass
-
36
relation.left_outer_joins_values |= other.left_outer_joins_values
-
else
-
21
associations, others = other.left_outer_joins_values.partition do |join|
-
27
case join
-
21
when Hash, Symbol, Array; true
-
end
-
end
-
-
21
join_dependency = other.construct_join_dependency(
-
associations, Arel::Nodes::OuterJoin
-
)
-
21
relation.left_outer_joins!(join_dependency, *others)
-
end
-
end
-
-
3
def merge_multi_values
-
39613
if other.reordering_value
-
# override any order specified in the original relation
-
9
relation.reorder!(*other.order_values)
-
39604
elsif other.order_values.any?
-
# merge in order_values from relation
-
4030
relation.order!(*other.order_values)
-
end
-
-
39613
extensions = other.extensions - relation.extensions
-
39613
relation.extending!(*extensions) if extensions.any?
-
end
-
-
3
def merge_single_values
-
39613
relation.lock_value ||= other.lock_value if other.lock_value
-
-
39613
unless other.create_with_value.blank?
-
3
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
-
end
-
end
-
-
3
def merge_clauses
-
39613
relation.from_clause = other.from_clause if replace_from_clause?
-
-
39613
where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
-
39613
relation.where_clause = where_clause unless where_clause.empty?
-
-
39610
having_clause = relation.having_clause.merge(other.having_clause)
-
39610
relation.having_clause = having_clause unless having_clause.empty?
-
end
-
-
3
def replace_from_clause?
-
39613
relation.from_clause.empty? && !other.from_clause.empty? &&
-
relation.klass.base_class == other.klass.base_class
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class PredicateBuilder # :nodoc:
-
3
require "active_record/relation/predicate_builder/array_handler"
-
3
require "active_record/relation/predicate_builder/basic_object_handler"
-
3
require "active_record/relation/predicate_builder/range_handler"
-
3
require "active_record/relation/predicate_builder/relation_handler"
-
3
require "active_record/relation/predicate_builder/association_query_value"
-
3
require "active_record/relation/predicate_builder/polymorphic_array_value"
-
-
# No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")).
-
# TODO: Remove the constant alias once Rails 6.1 has released.
-
3
BaseHandler = BasicObjectHandler
-
-
3
def initialize(table)
-
8168
@table = table
-
8168
@handlers = []
-
-
8168
register_handler(BasicObject, BasicObjectHandler.new(self))
-
8168
register_handler(Range, RangeHandler.new(self))
-
8168
register_handler(Relation, RelationHandler.new)
-
8168
register_handler(Array, ArrayHandler.new(self))
-
8168
register_handler(Set, ArrayHandler.new(self))
-
end
-
-
3
def build_from_hash(attributes, &block)
-
42337
attributes = convert_dot_notation_to_hash(attributes)
-
42337
expand_from_hash(attributes, &block)
-
end
-
-
3
def self.references(attributes)
-
42337
attributes.each_with_object([]) do |(key, value), result|
-
48586
if value.is_a?(Hash)
-
3574
result << key
-
45012
elsif key.include?(".")
-
408
result << key.split(".").first
-
end
-
end
-
end
-
-
# Define how a class is converted to Arel nodes when passed to +where+.
-
# The handler can be any object that responds to +call+, and will be used
-
# for any value that +===+ the class given. For example:
-
#
-
# MyCustomDateRange = Struct.new(:start, :end)
-
# handler = proc do |column, range|
-
# Arel::Nodes::Between.new(column,
-
# Arel::Nodes::And.new([range.start, range.end])
-
# )
-
# end
-
# ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler)
-
3
def register_handler(klass, handler)
-
40846
@handlers.unshift([klass, handler])
-
end
-
-
3
def [](attr_name, value, operator = nil)
-
891
build(table.arel_table[attr_name], value, operator)
-
end
-
-
3
def build(attribute, value, operator = nil)
-
61268
value = value.id if value.is_a?(Base)
-
61268
if operator ||= table.type(attribute.name).force_equality?(value) && :eq
-
909
bind = build_bind_attribute(attribute.name, value)
-
909
attribute.public_send(operator, bind)
-
else
-
60359
handler_for(value).call(attribute, value)
-
end
-
end
-
-
3
def build_bind_attribute(column_name, value)
-
91288
attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name))
-
91288
Arel::Nodes::BindParam.new(attr)
-
end
-
-
3
def resolve_arel_attribute(table_name, column_name, &block)
-
192
table.associated_table(table_name, &block).arel_table[column_name]
-
end
-
-
3
protected
-
3
def expand_from_hash(attributes, &block)
-
46728
return ["1=0"] if attributes.empty?
-
-
46722
attributes.flat_map do |key, value|
-
53064
if value.is_a?(Hash) && !table.has_column?(key)
-
table.associated_table(key, &block)
-
3955
.predicate_builder.expand_from_hash(value.stringify_keys)
-
49109
elsif table.associated_with?(key)
-
# Find the foreign key when using queries such as:
-
# Post.where(author: author)
-
#
-
# For polymorphic relationships, find the foreign key and type:
-
# PriceEstimate.where(estimate_of: treasure)
-
439
associated_table = table.associated_table(key)
-
439
if associated_table.polymorphic_association?
-
91
case value.is_a?(Array) ? value.first : value
-
when Base, Relation
-
78
value = [value] unless value.is_a?(Array)
-
78
klass = PolymorphicArrayValue
-
end
-
348
elsif associated_table.through_association?
-
next associated_table.predicate_builder.expand_from_hash(
-
associated_table.join_foreign_key => value
-
3
)
-
end
-
-
436
klass ||= AssociationQueryValue
-
436
queries = klass.new(associated_table, value).queries.map! do |query|
-
442
expand_from_hash(query)
-
end
-
-
436
grouping_queries(queries)
-
48670
elsif table.aggregated_with?(key)
-
75
mapping = table.reflect_on_aggregation(key).mapping
-
75
values = value.nil? ? [nil] : Array.wrap(value)
-
75
if mapping.length == 1 || values.empty?
-
30
column_name, aggr_attr = mapping.first
-
30
values = values.map do |object|
-
33
object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
-
end
-
30
build(table.arel_table[column_name], values)
-
else
-
45
queries = values.map do |object|
-
51
mapping.map do |field_attr, aggregate_attr|
-
153
build(table.arel_table[field_attr], object.try!(aggregate_attr))
-
end
-
end
-
-
45
grouping_queries(queries)
-
end
-
else
-
48595
build(table.arel_table[key], value)
-
end
-
end
-
end
-
-
3
private
-
3
attr_reader :table
-
-
3
def grouping_queries(queries)
-
481
if queries.one?
-
472
queries.first
-
else
-
30
queries.map! { |query| query.reduce(&:and) }
-
21
queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
-
9
Arel::Nodes::Grouping.new(queries)
-
end
-
end
-
-
3
def convert_dot_notation_to_hash(attributes)
-
42337
dot_notation = attributes.select do |k, v|
-
48586
k.include?(".") && !v.is_a?(Hash)
-
end
-
-
42337
dot_notation.each_key do |key|
-
408
table_name, column_name = key.split(".")
-
408
value = attributes.delete(key)
-
408
attributes[table_name] ||= {}
-
-
408
attributes[table_name] = attributes[table_name].merge(column_name => value)
-
end
-
-
42337
attributes
-
end
-
-
3
def handler_for(object)
-
325424
@handlers.detect { |klass, _| klass === object }.last
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/array/extract"
-
-
3
module ActiveRecord
-
3
class PredicateBuilder
-
3
class ArrayHandler # :nodoc:
-
3
def initialize(predicate_builder)
-
16336
@predicate_builder = predicate_builder
-
end
-
-
3
def call(attribute, value)
-
12128
return attribute.in([]) if value.empty?
-
-
706055
values = value.map { |x| x.is_a?(Base) ? x.id : x }
-
11952
nils = values.extract!(&:nil?)
-
705971
ranges = values.extract! { |v| v.is_a?(Range) }
-
-
11952
values_predicate =
-
case values.length
-
37
when 0 then NullPredicate
-
4113
when 1 then predicate_builder.build(attribute, values.first)
-
7802
else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
-
end
-
-
11952
unless nils.empty?
-
84
values_predicate = values_predicate.or(attribute.eq(nil))
-
end
-
-
11952
if ranges.empty?
-
11945
values_predicate
-
else
-
20
array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
-
7
array_predicates.inject(values_predicate, &:or)
-
end
-
end
-
-
3
private
-
3
attr_reader :predicate_builder
-
-
3
module NullPredicate # :nodoc:
-
3
def self.or(other)
-
37
other
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class PredicateBuilder
-
3
class AssociationQueryValue # :nodoc:
-
3
def initialize(associated_table, value)
-
358
@associated_table = associated_table
-
358
@value = value
-
end
-
-
3
def queries
-
358
[associated_table.join_foreign_key => ids]
-
end
-
-
3
private
-
3
attr_reader :associated_table, :value
-
-
3
def ids
-
358
case value
-
when Relation
-
27
value.select_values.empty? ? value.select(primary_key) : value
-
when Array
-
232
value.map { |v| convert_to_id(v) }
-
else
-
223
convert_to_id(value)
-
end
-
end
-
-
3
def primary_key
-
349
associated_table.join_primary_key
-
end
-
-
3
def convert_to_id(value)
-
347
case value
-
when Base
-
325
value._read_attribute(primary_key)
-
else
-
22
value
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class PredicateBuilder
-
3
class BasicObjectHandler # :nodoc:
-
3
def initialize(predicate_builder)
-
8168
@predicate_builder = predicate_builder
-
end
-
-
3
def call(attribute, value)
-
47999
bind = predicate_builder.build_bind_attribute(attribute.name, value)
-
47999
attribute.eq(bind)
-
end
-
-
3
private
-
3
attr_reader :predicate_builder
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class PredicateBuilder
-
3
class PolymorphicArrayValue # :nodoc:
-
3
def initialize(associated_table, values)
-
78
@associated_table = associated_table
-
78
@values = values
-
end
-
-
3
def queries
-
78
type_to_ids_mapping.map do |type, ids|
-
84
{
-
associated_table.join_foreign_type => type,
-
associated_table.join_foreign_key => ids
-
}
-
end
-
end
-
-
3
private
-
3
attr_reader :associated_table, :values
-
-
3
def type_to_ids_mapping
-
162
default_hash = Hash.new { |hsh, key| hsh[key] = [] }
-
78
values.each_with_object(default_hash) do |value, hash|
-
90
hash[klass(value).polymorphic_name] << convert_to_id(value)
-
end
-
end
-
-
3
def primary_key(value)
-
90
associated_table.join_primary_key(klass(value))
-
end
-
-
3
def klass(value)
-
180
case value
-
when Base
-
156
value.class
-
when Relation
-
24
value.klass
-
end
-
end
-
-
3
def convert_to_id(value)
-
90
case value
-
when Base
-
78
value._read_attribute(primary_key(value))
-
when Relation
-
12
value.select(primary_key(value))
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class PredicateBuilder
-
3
class RangeHandler # :nodoc:
-
3
RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
-
-
3
def initialize(predicate_builder)
-
8168
@predicate_builder = predicate_builder
-
end
-
-
3
def call(attribute, value)
-
136
begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
-
136
end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
-
136
attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
-
end
-
-
3
private
-
3
attr_reader :predicate_builder
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class PredicateBuilder
-
3
class RelationHandler # :nodoc:
-
3
def call(attribute, value)
-
90
if value.eager_loading?
-
3
value = value.send(:apply_join_dependency)
-
end
-
-
90
if value.select_values.empty?
-
42
value = value.select(value.table[value.klass.primary_key])
-
end
-
-
90
attribute.in(value.arel)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_model/attribute"
-
-
3
module ActiveRecord
-
3
class Relation
-
3
class QueryAttribute < ActiveModel::Attribute # :nodoc:
-
3
def type_cast(value)
-
88167
value
-
end
-
-
3
def value_for_database
-
131777
@value_for_database ||= super
-
end
-
-
3
def with_cast_value(value)
-
6496
QueryAttribute.new(name, value, type)
-
end
-
-
3
def nil?
-
45894
unless value_before_type_cast.is_a?(StatementCache::Substitute)
-
44533
value_before_type_cast.nil? ||
-
type.respond_to?(:subtype, true) && value_for_database.nil?
-
end
-
rescue ::RangeError
-
end
-
-
3
def infinite?
-
254
infinity?(value_before_type_cast) || infinity?(value_for_database)
-
rescue ::RangeError
-
end
-
-
3
def unboundable?
-
66010
if defined?(@_unboundable)
-
22393
@_unboundable
-
else
-
43617
value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
-
43561
@_unboundable = nil
-
end
-
rescue ::RangeError
-
56
@_unboundable = type.cast(value_before_type_cast) <=> 0
-
end
-
-
3
private
-
3
def infinity?(value)
-
465
value.respond_to?(:infinite?) && value.infinite?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/relation/from_clause"
-
3
require "active_record/relation/query_attribute"
-
3
require "active_record/relation/where_clause"
-
3
require "active_model/forbidden_attributes_protection"
-
3
require "active_support/core_ext/array/wrap"
-
-
3
module ActiveRecord
-
3
module QueryMethods
-
3
extend ActiveSupport::Concern
-
-
3
include ActiveModel::ForbiddenAttributesProtection
-
-
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
-
# In this case, #where must be chained with #not to return a new relation.
-
3
class WhereChain
-
3
def initialize(scope)
-
226
@scope = scope
-
end
-
-
# Returns a new relation expressing WHERE + NOT condition according to
-
# the conditions in the arguments.
-
#
-
# #not accepts conditions as a string, array, or hash. See QueryMethods#where for
-
# more details on each format.
-
#
-
# User.where.not("name = 'Jon'")
-
# # SELECT * FROM users WHERE NOT (name = 'Jon')
-
#
-
# User.where.not(["name = ?", "Jon"])
-
# # SELECT * FROM users WHERE NOT (name = 'Jon')
-
#
-
# User.where.not(name: "Jon")
-
# # SELECT * FROM users WHERE name != 'Jon'
-
#
-
# User.where.not(name: nil)
-
# # SELECT * FROM users WHERE name IS NOT NULL
-
#
-
# User.where.not(name: %w(Ko1 Nobu))
-
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
-
3
def not(opts, *rest)
-
217
where_clause = @scope.send(:build_where_clause, opts, rest)
-
-
211
if not_behaves_as_nor?(opts)
-
6
ActiveSupport::Deprecation.warn(<<~MSG.squish)
-
NOT conditions will no longer behave as NOR in Rails 6.1.
-
To continue using NOR conditions, NOT each condition individually
-
(`#{
-
opts.flat_map { |key, value|
-
9
if value.is_a?(Hash) && value.size > 1
-
9
value.map { |k, v| ".where.not(#{key.inspect} => { #{k.inspect} => ... })" }
-
else
-
6
".where.not(#{key.inspect} => ...)"
-
end
-
}.join
-
}`).
-
MSG
-
6
@scope.where_clause += where_clause.invert(:nor)
-
else
-
205
@scope.where_clause += where_clause.invert
-
end
-
-
211
@scope
-
end
-
-
# Returns a new relation with left outer joins and where clause to identify
-
# missing relations.
-
#
-
# For example, posts that are missing a related author:
-
#
-
# Post.where.missing(:author)
-
# # SELECT "posts".* FROM "posts"
-
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
-
# # WHERE "authors"."id" IS NULL
-
#
-
# Additionally, multiple relations can be combined. This will return posts
-
# that are missing both an author and any comments:
-
#
-
# Post.where.missing(:author, :comments)
-
# # SELECT "posts".* FROM "posts"
-
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
-
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
-
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
-
3
def missing(*args)
-
9
args.each do |arg|
-
12
reflection = @scope.klass._reflect_on_association(arg)
-
12
opts = { reflection.table_name => { reflection.association_primary_key => nil } }
-
12
@scope.left_outer_joins!(arg)
-
12
@scope.where!(opts)
-
end
-
-
9
@scope
-
end
-
-
3
private
-
3
def not_behaves_as_nor?(opts)
-
208
return false unless opts.is_a?(Hash)
-
-
413
opts.any? { |k, v| v.is_a?(Hash) && v.size > 1 } ||
-
opts.size > 1
-
end
-
end
-
-
3
FROZEN_EMPTY_ARRAY = [].freeze
-
3
FROZEN_EMPTY_HASH = {}.freeze
-
-
3
Relation::VALUE_METHODS.each do |name|
-
78
method_name, default =
-
case name
-
when *Relation::MULTI_VALUE_METHODS
-
39
["#{name}_values", "FROZEN_EMPTY_ARRAY"]
-
when *Relation::SINGLE_VALUE_METHODS
-
30
["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
-
when *Relation::CLAUSE_METHODS
-
9
["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
-
end
-
-
78
class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{method_name} # def includes_values
-
@values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
-
end # end
-
-
def #{method_name}=(value) # def includes_values=(value)
-
assert_mutability! # assert_mutability!
-
@values[:#{name}] = value # @values[:includes] = value
-
end # end
-
CODE
-
end
-
-
3
alias extensions extending_values
-
-
# Specify relationships to be included in the result set. For
-
# example:
-
#
-
# users = User.includes(:address)
-
# users.each do |user|
-
# user.address.city
-
# end
-
#
-
# allows you to access the +address+ attribute of the +User+ model without
-
# firing an additional query. This will often result in a
-
# performance improvement over a simple join.
-
#
-
# You can also specify multiple relationships, like this:
-
#
-
# users = User.includes(:address, :friends)
-
#
-
# Loading nested relationships is possible using a Hash:
-
#
-
# users = User.includes(:address, friends: [:address, :followers])
-
#
-
# === conditions
-
#
-
# If you want to add string conditions to your included models, you'll have
-
# to explicitly reference them. For example:
-
#
-
# User.includes(:posts).where('posts.name = ?', 'example')
-
#
-
# Will throw an error, but this will work:
-
#
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
-
#
-
# Note that #includes works with association names while #references needs
-
# the actual table name.
-
#
-
# If you pass the conditions via hash, you don't need to call #references
-
# explicitly, as #where references the tables for you. For example, this
-
# will work correctly:
-
#
-
# User.includes(:posts).where(posts: { name: 'example' })
-
3
def includes(*args)
-
1412
check_if_method_has_arguments!(:includes, args)
-
1409
spawn.includes!(*args)
-
end
-
-
3
def includes!(*args) # :nodoc:
-
2027
self.includes_values |= args
-
2027
self
-
end
-
-
# Forces eager loading by performing a LEFT OUTER JOIN on +args+:
-
#
-
# User.eager_load(:posts)
-
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
-
# # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
-
# # "users"."id"
-
3
def eager_load(*args)
-
243
check_if_method_has_arguments!(:eager_load, args)
-
240
spawn.eager_load!(*args)
-
end
-
-
3
def eager_load!(*args) # :nodoc:
-
249
self.eager_load_values |= args
-
249
self
-
end
-
-
# Allows preloading of +args+, in the same way that #includes does:
-
#
-
# User.preload(:posts)
-
# # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
-
3
def preload(*args)
-
240
check_if_method_has_arguments!(:preload, args)
-
237
spawn.preload!(*args)
-
end
-
-
3
def preload!(*args) # :nodoc:
-
243
self.preload_values |= args
-
243
self
-
end
-
-
# Extracts a named +association+ from the relation. The named association is first preloaded,
-
# then the individual association records are collected from the relation. Like so:
-
#
-
# account.memberships.extract_associated(:user)
-
# # => Returns collection of User records
-
#
-
# This is short-hand for:
-
#
-
# account.memberships.preload(:user).collect(&:user)
-
3
def extract_associated(association)
-
6
preload(association).collect(&association)
-
end
-
-
# Use to indicate that the given +table_names+ are referenced by an SQL string,
-
# and should therefore be JOINed in any query rather than loaded separately.
-
# This method only works in conjunction with #includes.
-
# See #includes for more details.
-
#
-
# User.includes(:posts).where("posts.name = 'foo'")
-
# # Doesn't JOIN the posts table, resulting in an error.
-
#
-
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
-
# # Query now knows the string references posts, so adds a JOIN
-
3
def references(*table_names)
-
199
check_if_method_has_arguments!(:references, table_names)
-
196
spawn.references!(*table_names)
-
end
-
-
3
def references!(*table_names) # :nodoc:
-
7509
table_names.map!(&:to_s)
-
-
7509
self.references_values |= table_names
-
7509
self
-
end
-
-
# Works in two unique ways.
-
#
-
# First: takes a block so it can be used just like <tt>Array#select</tt>.
-
#
-
# Model.all.select { |m| m.field == value }
-
#
-
# This will build an array of objects from the database for the scope,
-
# converting them into an array and iterating through them using
-
# <tt>Array#select</tt>.
-
#
-
# Second: Modifies the SELECT statement for the query so that only certain
-
# fields are retrieved:
-
#
-
# Model.select(:field)
-
# # => [#<Model id: nil, field: "value">]
-
#
-
# Although in the above example it looks as though this method returns an
-
# array, it actually returns a relation object and can have other query
-
# methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
-
#
-
# The argument to the method can also be an array of fields.
-
#
-
# Model.select(:field, :other_field, :and_one_more)
-
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
-
#
-
# You can also use one or more strings, which will be used unchanged as SELECT fields.
-
#
-
# Model.select('field AS field_one', 'other_field AS field_two')
-
# # => [#<Model id: nil, field: "value", other_field: "value">]
-
#
-
# If an alias was specified, it will be accessible from the resulting objects:
-
#
-
# Model.select('field AS field_one').first.field_one
-
# # => "value"
-
#
-
# Accessing attributes of an object that do not have fields retrieved by a select
-
# except +id+ will throw ActiveModel::MissingAttributeError:
-
#
-
# Model.select(:field).first.other_field
-
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
-
3
def select(*fields)
-
2898
if block_given?
-
1939
if fields.any?
-
3
raise ArgumentError, "`select' with block doesn't take arguments."
-
end
-
-
1936
return super()
-
end
-
-
959
check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
-
956
spawn._select!(*fields)
-
end
-
-
3
def _select!(*fields) # :nodoc:
-
2988
self.select_values |= fields
-
2988
self
-
end
-
-
# Allows you to change a previously set select statement.
-
#
-
# Post.select(:title, :body)
-
# # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
-
#
-
# Post.select(:title, :body).reselect(:created_at)
-
# # SELECT `posts`.`created_at` FROM `posts`
-
#
-
# This is short-hand for <tt>unscope(:select).select(fields)</tt>.
-
# Note that we're unscoping the entire select statement.
-
3
def reselect(*args)
-
15
check_if_method_has_arguments!(:reselect, args)
-
12
spawn.reselect!(*args)
-
end
-
-
# Same as #reselect but operates on relation in-place instead of copying.
-
3
def reselect!(*args) # :nodoc:
-
12
self.select_values = args
-
12
self
-
end
-
-
# Allows to specify a group attribute:
-
#
-
# User.group(:name)
-
# # SELECT "users".* FROM "users" GROUP BY name
-
#
-
# Returns an array with distinct records based on the +group+ attribute:
-
#
-
# User.select([:id, :name])
-
# # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
-
#
-
# User.group(:name)
-
# # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
-
#
-
# User.group('name AS grouped_name, age')
-
# # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
-
#
-
# Passing in an array of attributes to group by is also supported.
-
#
-
# User.select([:id, :first_name]).group(:id, :first_name).first(3)
-
# # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
-
3
def group(*args)
-
385
check_if_method_has_arguments!(:group, args)
-
382
spawn.group!(*args)
-
end
-
-
3
def group!(*args) # :nodoc:
-
472
self.group_values += args
-
472
self
-
end
-
-
# Allows to specify an order attribute:
-
#
-
# User.order(:name)
-
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
-
#
-
# User.order(email: :desc)
-
# # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
-
#
-
# User.order(:name, email: :desc)
-
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
-
#
-
# User.order('name')
-
# # SELECT "users".* FROM "users" ORDER BY name
-
#
-
# User.order('name DESC')
-
# # SELECT "users".* FROM "users" ORDER BY name DESC
-
#
-
# User.order('name DESC, email')
-
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
-
3
def order(*args)
-
9084
check_if_method_has_arguments!(:order, args) do
-
9081
sanitize_order_arguments(args)
-
end
-
9078
spawn.order!(*args)
-
end
-
-
# Same as #order but operates on relation in-place instead of copying.
-
3
def order!(*args) # :nodoc:
-
13411
preprocess_order_args(args) unless args.empty?
-
13391
self.order_values |= args
-
13391
self
-
end
-
-
# Replaces any existing order defined on the relation with the specified order.
-
#
-
# User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
-
#
-
# Subsequent calls to order on the same relation will be appended. For example:
-
#
-
# User.order('email DESC').reorder('id ASC').order('name ASC')
-
#
-
# generates a query with 'ORDER BY id ASC, name ASC'.
-
3
def reorder(*args)
-
306
check_if_method_has_arguments!(:reorder, args) do
-
303
sanitize_order_arguments(args) unless args.all?(&:blank?)
-
end
-
303
spawn.reorder!(*args)
-
end
-
-
# Same as #reorder but operates on relation in-place instead of copying.
-
3
def reorder!(*args) # :nodoc:
-
318
preprocess_order_args(args) unless args.all?(&:blank?)
-
318
self.reordering_value = true
-
318
self.order_values = args
-
318
self
-
end
-
-
3
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
-
:limit, :offset, :joins, :left_outer_joins, :annotate,
-
:includes, :from, :readonly, :having, :optimizer_hints])
-
-
# Removes an unwanted relation that is already defined on a chain of relations.
-
# This is useful when passing around chains of relations and would like to
-
# modify the relations without reconstructing the entire chain.
-
#
-
# User.order('email DESC').unscope(:order) == User.all
-
#
-
# The method arguments are symbols which correspond to the names of the methods
-
# which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
-
# The method can also be called with multiple arguments. For example:
-
#
-
# User.order('email DESC').select('id').where(name: "John")
-
# .unscope(:order, :select, :where) == User.all
-
#
-
# One can additionally pass a hash as an argument to unscope specific +:where+ values.
-
# This is done by passing a hash with a single key-value pair. The key should be
-
# +:where+ and the value should be the where value to unscope. For example:
-
#
-
# User.where(name: "John", active: true).unscope(where: :name)
-
# == User.where(active: true)
-
#
-
# This method is similar to #except, but unlike
-
# #except, it persists across merges:
-
#
-
# User.order('email').merge(User.except(:order))
-
# == User.order('email')
-
#
-
# User.order('email').merge(User.unscope(:order))
-
# == User.all
-
#
-
# This means it can be used in association definitions:
-
#
-
# has_many :comments, -> { unscope(where: :trashed) }
-
#
-
3
def unscope(*args)
-
4135
check_if_method_has_arguments!(:unscope, args)
-
4129
spawn.unscope!(*args)
-
end
-
-
3
def unscope!(*args) # :nodoc:
-
6981
self.unscope_values += args
-
-
6981
args.each do |scope|
-
4357
case scope
-
when Symbol
-
4012
scope = :left_outer_joins if scope == :left_joins
-
4012
if !VALID_UNSCOPING_VALUES.include?(scope)
-
9
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
-
end
-
4003
assert_mutability!
-
4003
@values.delete(scope)
-
when Hash
-
336
scope.each do |key, target_value|
-
336
if key != :where
-
6
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
-
end
-
-
330
target_values = resolve_arel_attributes(Array.wrap(target_value))
-
330
self.where_clause = where_clause.except(*target_values)
-
end
-
else
-
9
raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
-
end
-
end
-
-
6957
self
-
end
-
-
# Performs a joins on +args+. The given symbol(s) should match the name of
-
# the association(s).
-
#
-
# User.joins(:posts)
-
# # SELECT "users".*
-
# # FROM "users"
-
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
-
#
-
# Multiple joins:
-
#
-
# User.joins(:posts, :account)
-
# # SELECT "users".*
-
# # FROM "users"
-
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
-
# # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
-
#
-
# Nested joins:
-
#
-
# User.joins(posts: [:comments])
-
# # SELECT "users".*
-
# # FROM "users"
-
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
-
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
-
#
-
# You can use strings in order to customize your joins:
-
#
-
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
-
# # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
-
3
def joins(*args)
-
927
check_if_method_has_arguments!(:joins, args)
-
924
spawn.joins!(*args)
-
end
-
-
3
def joins!(*args) # :nodoc:
-
4916
self.joins_values |= args
-
4916
self
-
end
-
-
# Performs a left outer joins on +args+:
-
#
-
# User.left_outer_joins(:posts)
-
# => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
-
#
-
3
def left_outer_joins(*args)
-
207
check_if_method_has_arguments!(__callee__, args)
-
204
spawn.left_outer_joins!(*args)
-
end
-
3
alias :left_joins :left_outer_joins
-
-
3
def left_outer_joins!(*args) # :nodoc:
-
279
self.left_outer_joins_values |= args
-
279
self
-
end
-
-
# Returns a new relation, which is the result of filtering the current relation
-
# according to the conditions in the arguments.
-
#
-
# #where accepts conditions in one of several formats. In the examples below, the resulting
-
# SQL is given as an illustration; the actual query generated may be different depending
-
# on the database adapter.
-
#
-
# === string
-
#
-
# A single string, without additional arguments, is passed to the query
-
# constructor as an SQL fragment, and used in the where clause of the query.
-
#
-
# Client.where("orders_count = '2'")
-
# # SELECT * from clients where orders_count = '2';
-
#
-
# Note that building your own string from user input may expose your application
-
# to injection attacks if not done properly. As an alternative, it is recommended
-
# to use one of the following methods.
-
#
-
# === array
-
#
-
# If an array is passed, then the first element of the array is treated as a template, and
-
# the remaining elements are inserted into the template to generate the condition.
-
# Active Record takes care of building the query to avoid injection attacks, and will
-
# convert from the ruby type to the database type where needed. Elements are inserted
-
# into the string in the order in which they appear.
-
#
-
# User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# Alternatively, you can use named placeholders in the template, and pass a hash as the
-
# second element of the array. The names in the template are replaced with the corresponding
-
# values from the hash.
-
#
-
# User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# This can make for more readable code in complex queries.
-
#
-
# Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
-
# than the previous methods; you are responsible for ensuring that the values in the template
-
# are properly quoted. The values are passed to the connector for quoting, but the caller
-
# is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
-
# the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
-
#
-
# User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# If #where is called with multiple arguments, these are treated as if they were passed as
-
# the elements of a single array.
-
#
-
# User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# When using strings to specify conditions, you can use any operator available from
-
# the database. While this provides the most flexibility, you can also unintentionally introduce
-
# dependencies on the underlying database. If your code is intended for general consumption,
-
# test with multiple database backends.
-
#
-
# === hash
-
#
-
# #where will also accept a hash condition, in which the keys are fields and the values
-
# are values to be searched for.
-
#
-
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
-
#
-
# User.where({ name: "Joe", email: "joe@example.com" })
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
-
#
-
# User.where({ name: ["Alice", "Bob"]})
-
# # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
-
#
-
# User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
-
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
-
#
-
# In the case of a belongs_to relationship, an association key can be used
-
# to specify the model if an ActiveRecord object is used as the value.
-
#
-
# author = Author.find(1)
-
#
-
# # The following queries will be equivalent:
-
# Post.where(author: author)
-
# Post.where(author_id: author)
-
#
-
# This also works with polymorphic belongs_to relationships:
-
#
-
# treasure = Treasure.create(name: 'gold coins')
-
# treasure.price_estimates << PriceEstimate.create(price: 125)
-
#
-
# # The following queries will be equivalent:
-
# PriceEstimate.where(estimate_of: treasure)
-
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
-
#
-
# === Joins
-
#
-
# If the relation is the result of a join, you may create a condition which uses any of the
-
# tables in the join. For string and array conditions, use the table name in the condition.
-
#
-
# User.joins(:posts).where("posts.created_at < ?", Time.now)
-
#
-
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
-
#
-
# User.joins(:posts).where({ "posts.published" => true })
-
# User.joins(:posts).where({ posts: { published: true } })
-
#
-
# === no argument
-
#
-
# If no argument is passed, #where returns a new instance of WhereChain, that
-
# can be chained with #not to return a new relation that negates the where clause.
-
#
-
# User.where.not(name: "Jon")
-
# # SELECT * FROM users WHERE name != 'Jon'
-
#
-
# See WhereChain for more details on #not.
-
#
-
# === blank condition
-
#
-
# If the condition is any blank-ish object, then #where is a no-op and returns
-
# the current relation.
-
3
def where(opts = :chain, *rest)
-
30560
if :chain == opts
-
226
WhereChain.new(spawn)
-
30334
elsif opts.blank?
-
237
self
-
else
-
30097
spawn.where!(opts, *rest)
-
end
-
end
-
-
3
def where!(opts, *rest) # :nodoc:
-
56495
self.where_clause += build_where_clause(opts, rest)
-
56462
self
-
end
-
-
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
-
#
-
# Post.where(trashed: true).where(trashed: false)
-
# # WHERE `trashed` = 1 AND `trashed` = 0
-
#
-
# Post.where(trashed: true).rewhere(trashed: false)
-
# # WHERE `trashed` = 0
-
#
-
# Post.where(active: true).where(trashed: true).rewhere(trashed: false)
-
# # WHERE `active` = 1 AND `trashed` = 0
-
#
-
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
-
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
-
3
def rewhere(conditions)
-
81
scope = spawn
-
81
where_clause = scope.build_where_clause(conditions)
-
-
81
scope.unscope!(where: where_clause.extract_attributes)
-
81
scope.where_clause += where_clause
-
81
scope
-
end
-
-
# Returns a new relation, which is the logical intersection of this relation and the one passed
-
# as an argument.
-
#
-
# The two relations must be structurally compatible: they must be scoping the same model, and
-
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
-
# present).
-
#
-
# Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
-
# # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
-
#
-
3
def and(other)
-
27
if other.is_a?(Relation)
-
27
spawn.and!(other)
-
else
-
raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
-
end
-
end
-
-
3
def and!(other) # :nodoc:
-
27
incompatible_values = structurally_incompatible_values_for(other)
-
-
27
unless incompatible_values.empty?
-
raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
-
end
-
-
27
self.where_clause |= other.where_clause
-
27
self.having_clause |= other.having_clause
-
27
self.references_values |= other.references_values
-
-
27
self
-
end
-
-
# Returns a new relation, which is the logical union of this relation and the one passed as an
-
# argument.
-
#
-
# The two relations must be structurally compatible: they must be scoping the same model, and
-
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
-
# present).
-
#
-
# Post.where("id = 1").or(Post.where("author_id = 3"))
-
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
-
#
-
3
def or(other)
-
6149
if other.is_a?(Relation)
-
6146
spawn.or!(other)
-
else
-
3
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
-
end
-
end
-
-
3
def or!(other) # :nodoc:
-
6146
incompatible_values = structurally_incompatible_values_for(other)
-
-
6146
unless incompatible_values.empty?
-
6
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
-
end
-
-
6140
self.where_clause = self.where_clause.or(other.where_clause)
-
6140
self.having_clause = having_clause.or(other.having_clause)
-
6140
self.references_values |= other.references_values
-
-
6140
self
-
end
-
-
# Allows to specify a HAVING clause. Note that you can't use HAVING
-
# without also specifying a GROUP clause.
-
#
-
# Order.having('SUM(price) > 30').group('user_id')
-
3
def having(opts, *rest)
-
77
opts.blank? ? self : spawn.having!(opts, *rest)
-
end
-
-
3
def having!(opts, *rest) # :nodoc:
-
71
self.having_clause += build_having_clause(opts, rest)
-
68
self
-
end
-
-
# Specifies a limit for the number of records to retrieve.
-
#
-
# User.limit(10) # generated SQL has 'LIMIT 10'
-
#
-
# User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
-
3
def limit(value)
-
17545
spawn.limit!(value)
-
end
-
-
3
def limit!(value) # :nodoc:
-
25089
self.limit_value = value
-
25083
self
-
end
-
-
# Specifies the number of rows to skip before returning rows.
-
#
-
# User.offset(10) # generated SQL has "OFFSET 10"
-
#
-
# Should be used with order.
-
#
-
# User.offset(10).order("name ASC")
-
3
def offset(value)
-
303
spawn.offset!(value)
-
end
-
-
3
def offset!(value) # :nodoc:
-
390
self.offset_value = value
-
390
self
-
end
-
-
# Specifies locking settings (default to +true+). For more information
-
# on locking, please see ActiveRecord::Locking.
-
3
def lock(locks = true)
-
38
spawn.lock!(locks)
-
end
-
-
3
def lock!(locks = true) # :nodoc:
-
44
case locks
-
when String, TrueClass, NilClass
-
41
self.lock_value = locks || true
-
else
-
3
self.lock_value = false
-
end
-
-
44
self
-
end
-
-
# Returns a chainable relation with zero records.
-
#
-
# The returned relation implements the Null Object pattern. It is an
-
# object with defined null behavior and always returns an empty array of
-
# records without querying the database.
-
#
-
# Any subsequent condition chained to the returned relation will continue
-
# generating an empty relation and will not fire any query to the database.
-
#
-
# Used in cases where a method or scope could return zero records but the
-
# result needs to be chainable.
-
#
-
# For example:
-
#
-
# @posts = current_user.visible_posts.where(name: params[:name])
-
# # the visible_posts method is expected to return a chainable Relation
-
#
-
# def visible_posts
-
# case role
-
# when 'Country Manager'
-
# Post.where(country: country)
-
# when 'Reviewer'
-
# Post.published
-
# when 'Bad User'
-
# Post.none # It can't be chained if [] is returned.
-
# end
-
# end
-
#
-
3
def none
-
135
spawn.none!
-
end
-
-
3
def none! # :nodoc:
-
925
where!("1=0").extending!(NullRelation)
-
end
-
-
# Sets readonly attributes for the returned relation. If value is
-
# true (default), attempting to update a record will result in an error.
-
#
-
# users = User.readonly
-
# users.first.save
-
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
-
3
def readonly(value = true)
-
153
spawn.readonly!(value)
-
end
-
-
3
def readonly!(value = true) # :nodoc:
-
237
self.readonly_value = value
-
237
self
-
end
-
-
# Sets the returned relation to strict_loading mode. This will raise an error
-
# if the record tries to lazily load an association.
-
#
-
# user = User.strict_loading.first
-
# user.comments.to_a
-
# => ActiveRecord::StrictLoadingViolationError
-
3
def strict_loading(value = true)
-
33
spawn.strict_loading!(value)
-
end
-
-
3
def strict_loading!(value = true) # :nodoc:
-
39
self.strict_loading_value = value
-
39
self
-
end
-
-
# Sets attributes to be used when creating new records from a
-
# relation object.
-
#
-
# users = User.where(name: 'Oscar')
-
# users.new.name # => 'Oscar'
-
#
-
# users = users.create_with(name: 'DHH')
-
# users.new.name # => 'DHH'
-
#
-
# You can pass +nil+ to #create_with to reset attributes:
-
#
-
# users = users.create_with(nil)
-
# users.new.name # => 'Oscar'
-
3
def create_with(value)
-
90
spawn.create_with!(value)
-
end
-
-
3
def create_with!(value) # :nodoc:
-
93
if value
-
90
value = sanitize_forbidden_attributes(value)
-
87
self.create_with_value = create_with_value.merge(value)
-
else
-
3
self.create_with_value = FROZEN_EMPTY_HASH
-
end
-
-
90
self
-
end
-
-
# Specifies table from which the records will be fetched. For example:
-
#
-
# Topic.select('title').from('posts')
-
# # SELECT title FROM posts
-
#
-
# Can accept other relation objects. For example:
-
#
-
# Topic.select('title').from(Topic.approved)
-
# # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
-
#
-
# Topic.select('a.title').from(Topic.approved, :a)
-
# # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
-
#
-
3
def from(value, subquery_name = nil)
-
135
spawn.from!(value, subquery_name)
-
end
-
-
3
def from!(value, subquery_name = nil) # :nodoc:
-
138
self.from_clause = Relation::FromClause.new(value, subquery_name)
-
138
self
-
end
-
-
# Specifies whether the records should be unique or not. For example:
-
#
-
# User.select(:name)
-
# # Might return two records with the same name
-
#
-
# User.select(:name).distinct
-
# # Returns 1 record per distinct name
-
#
-
# User.select(:name).distinct.distinct(false)
-
# # You can also remove the uniqueness
-
3
def distinct(value = true)
-
571
spawn.distinct!(value)
-
end
-
-
# Like #distinct, but modifies relation in place.
-
3
def distinct!(value = true) # :nodoc:
-
5428
self.distinct_value = value
-
5428
self
-
end
-
-
# Used to extend a scope with additional methods, either through
-
# a module or through a block provided.
-
#
-
# The object returned is a relation, which can be further extended.
-
#
-
# === Using a module
-
#
-
# module Pagination
-
# def page(number)
-
# # pagination code goes here
-
# end
-
# end
-
#
-
# scope = Model.all.extending(Pagination)
-
# scope.page(params[:page])
-
#
-
# You can also pass a list of modules:
-
#
-
# scope = Model.all.extending(Pagination, SomethingElse)
-
#
-
# === Using a block
-
#
-
# scope = Model.all.extending do
-
# def page(number)
-
# # pagination code goes here
-
# end
-
# end
-
# scope.page(params[:page])
-
#
-
# You can also use a block and a module list:
-
#
-
# scope = Model.all.extending(Pagination) do
-
# def per_page(number)
-
# # pagination code goes here
-
# end
-
# end
-
3
def extending(*modules, &block)
-
2904
if modules.any? || block
-
2904
spawn.extending!(*modules, &block)
-
else
-
self
-
end
-
end
-
-
3
def extending!(*modules, &block) # :nodoc:
-
16627
modules << Module.new(&block) if block
-
16627
modules.flatten!
-
-
16627
self.extending_values += modules
-
16624
extend(*extending_values) if extending_values.any?
-
-
16624
self
-
end
-
-
# Specify optimizer hints to be used in the SELECT statement.
-
#
-
# Example (for MySQL):
-
#
-
# Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
-
# # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
-
#
-
# Example (for PostgreSQL with pg_hint_plan):
-
#
-
# Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
-
# # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
-
3
def optimizer_hints(*args)
-
21
check_if_method_has_arguments!(:optimizer_hints, args)
-
18
spawn.optimizer_hints!(*args)
-
end
-
-
3
def optimizer_hints!(*args) # :nodoc:
-
24
self.optimizer_hints_values |= args
-
24
self
-
end
-
-
# Reverse the existing order clause on the relation.
-
#
-
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
-
3
def reverse_order
-
80
spawn.reverse_order!
-
end
-
-
3
def reverse_order! # :nodoc:
-
377
orders = order_values.compact_blank
-
377
self.order_values = reverse_sql_order(orders)
-
354
self
-
end
-
-
3
def skip_query_cache!(value = true) # :nodoc:
-
291
self.skip_query_cache_value = value
-
291
self
-
end
-
-
3
def skip_preloading! # :nodoc:
-
6
self.skip_preloading_value = true
-
6
self
-
end
-
-
# Adds an SQL comment to queries generated from this relation. For example:
-
#
-
# User.annotate("selecting user names").select(:name)
-
# # SELECT "users"."name" FROM "users" /* selecting user names */
-
#
-
# User.annotate("selecting", "user", "names").select(:name)
-
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
-
#
-
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
-
3
def annotate(*args)
-
144
check_if_method_has_arguments!(:annotate, args)
-
141
spawn.annotate!(*args)
-
end
-
-
# Like #annotate, but modifies relation in place.
-
3
def annotate!(*args) # :nodoc:
-
210
self.annotate_values += args
-
210
self
-
end
-
-
# Deduplicate multiple values.
-
3
def uniq!(name)
-
6
if values = @values[name]
-
6
values.uniq! if values.is_a?(Array) && !values.empty?
-
end
-
6
self
-
end
-
-
# Returns the Arel object associated with the relation.
-
3
def arel(aliases = nil) # :nodoc:
-
57617
@arel ||= build_arel(aliases)
-
end
-
-
3
def construct_join_dependency(associations, join_type) # :nodoc:
-
4018
ActiveRecord::Associations::JoinDependency.new(
-
klass, table, associations, join_type
-
)
-
end
-
-
3
protected
-
3
def build_subquery(subquery_alias, select_value) # :nodoc:
-
273
subquery = except(:optimizer_hints).arel.as(subquery_alias)
-
-
273
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
-
273
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
-
end
-
end
-
-
3
def build_where_clause(opts, rest = []) # :nodoc:
-
56864
opts = sanitize_forbidden_attributes(opts)
-
-
56852
case opts
-
when String, Array
-
2419
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
-
when Hash
-
42337
opts = opts.stringify_keys
-
42337
references = PredicateBuilder.references(opts)
-
42337
self.references_values |= references unless references.empty?
-
-
42337
parts = predicate_builder.build_from_hash(opts) do |table_name|
-
493
lookup_reflection_from_join_dependencies(table_name)
-
end
-
when Arel::Nodes::Node
-
12090
parts = [opts]
-
else
-
6
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
-
end
-
-
56828
Relation::WhereClause.new(parts)
-
end
-
3
alias :build_having_clause :build_where_clause
-
-
3
private
-
3
def lookup_reflection_from_join_dependencies(table_name)
-
520
each_join_dependencies do |join|
-
640
return join.reflection if table_name == join.table_name
-
end
-
nil
-
end
-
-
3
def each_join_dependencies(join_dependencies = build_join_dependencies)
-
598
join_dependencies.each do |join_dependency|
-
628
join_dependency.each do |join|
-
841
yield join
-
end
-
end
-
end
-
-
3
def build_join_dependencies
-
595
associations = joins_values | left_outer_joins_values
-
595
associations |= eager_load_values unless eager_load_values.empty?
-
595
associations |= includes_values unless includes_values.empty?
-
-
595
join_dependencies = []
-
595
join_dependencies.unshift construct_join_dependency(
-
select_association_list(associations, join_dependencies), nil
-
)
-
end
-
-
3
def assert_mutability!
-
220434
raise ImmutableRelation if @loaded
-
220422
raise ImmutableRelation if defined?(@arel) && @arel
-
end
-
-
3
def build_arel(aliases)
-
41345
arel = Arel::SelectManager.new(table)
-
-
41345
build_joins(arel, aliases)
-
-
41342
arel.where(where_clause.ast) unless where_clause.empty?
-
41342
arel.having(having_clause.ast) unless having_clause.empty?
-
41342
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
-
41333
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
-
41333
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
-
-
41333
build_order(arel)
-
41333
build_select(arel)
-
-
41333
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
-
41333
arel.distinct(distinct_value)
-
41333
arel.from(build_from) unless from_clause.empty?
-
41333
arel.lock(lock_value) if lock_value
-
-
41333
unless annotate_values.empty?
-
87
annotates = annotate_values
-
87
annotates = annotates.uniq if annotates.size > 1
-
87
unless annotates == annotate_values
-
9
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Duplicated query annotations are no longer shown in queries in Rails 6.2.
-
To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
-
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
-
MSG
-
9
annotates = annotate_values
-
end
-
87
arel.comment(*annotates)
-
end
-
-
41333
arel
-
end
-
-
3
def build_cast_value(name, value)
-
20666
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
-
20666
Arel::Nodes::BindParam.new(cast_value)
-
end
-
-
3
def build_from
-
123
opts = from_clause.value
-
123
name = from_clause.name
-
123
case opts
-
when Relation
-
54
if opts.eager_loading?
-
9
opts = opts.send(:apply_join_dependency)
-
end
-
54
name ||= "subquery"
-
54
opts.arel.as(name.to_s)
-
else
-
69
opts
-
end
-
end
-
-
3
def select_association_list(associations, stashed_joins = nil)
-
5186
result = []
-
5186
associations.each do |association|
-
1377
case association
-
when Hash, Symbol, Array
-
1200
result << association
-
when ActiveRecord::Associations::JoinDependency
-
123
stashed_joins&.<< association
-
else
-
54
yield association if block_given?
-
end
-
end
-
5183
result
-
end
-
-
3
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
-
end
-
-
3
def build_join_buckets
-
16234
buckets = Hash.new { |h, k| h[k] = [] }
-
-
4093
unless left_outer_joins_values.empty?
-
180
stashed_left_joins = []
-
180
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
-
3
raise ArgumentError, "only Hash, Symbol and Array are allowed"
-
end
-
-
177
if joins_values.empty?
-
129
buckets[:association_join] = left_joins
-
129
buckets[:stashed_join] = stashed_left_joins
-
129
return buckets, Arel::Nodes::OuterJoin
-
else
-
48
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
-
end
-
end
-
-
3961
joins = joins_values.dup
-
3961
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
-
1211
stashed_eager_load = joins.pop if joins.last.base_klass == klass
-
end
-
-
3961
joins.each_with_index do |join, i|
-
3242
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
-
end
-
-
3961
while joins.first.is_a?(Arel::Nodes::Join)
-
2255
join_node = joins.shift
-
2255
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
-
18
buckets[:join_node] << join_node
-
else
-
2237
buckets[:leading_join] << join_node
-
end
-
end
-
-
3961
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
-
18
if join.is_a?(Arel::Nodes::Join)
-
18
buckets[:join_node] << join
-
else
-
raise "unknown class: %s" % join.class.name
-
end
-
end
-
-
3961
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
-
3961
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
-
-
3961
return buckets, Arel::Nodes::InnerJoin
-
end
-
-
3
def build_joins(manager, aliases)
-
41345
return if joins_values.empty? && left_outer_joins_values.empty?
-
-
4093
buckets, join_type = build_join_buckets
-
-
4090
association_joins = buckets[:association_join]
-
4090
stashed_joins = buckets[:stashed_join]
-
4090
leading_joins = buckets[:leading_join]
-
4090
join_nodes = buckets[:join_node]
-
-
4090
join_sources = manager.join_sources
-
4090
join_sources.concat(leading_joins) unless leading_joins.empty?
-
-
4090
unless association_joins.empty? && stashed_joins.empty?
-
2114
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
-
2114
join_dependency = construct_join_dependency(association_joins, join_type)
-
2114
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
-
end
-
-
4090
join_sources.concat(join_nodes) unless join_nodes.empty?
-
end
-
-
3
def build_select(arel)
-
41333
if select_values.any?
-
8814
arel.project(*arel_columns(select_values))
-
32519
elsif klass.ignored_columns.any?
-
25220
arel.project(*klass.column_names.map { |field| table[field] })
-
else
-
29997
arel.project(table[Arel.star])
-
end
-
end
-
-
3
def arel_columns(columns)
-
11353
columns.flat_map do |field|
-
11892
case field
-
when Symbol
-
1778
arel_column(field.to_s) do |attr_name|
-
66
connection.quote_table_name(attr_name)
-
end
-
when String
-
3121
arel_column(field, &:itself)
-
when Proc
-
535
field.call
-
else
-
6458
field
-
end
-
end
-
end
-
-
3
def arel_column(field)
-
10419
field = klass.attribute_aliases[field] || field
-
10419
from = from_clause.name || from_clause.value
-
-
10419
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
-
4660
table[field]
-
5759
elsif field.match?(/\A\w+\.\w+\z/)
-
180
table, column = field.split(".")
-
180
predicate_builder.resolve_arel_attribute(table, column) do
-
27
lookup_reflection_from_join_dependencies(table)
-
end
-
else
-
5579
yield field
-
end
-
end
-
-
3
def table_name_matches?(from)
-
90
table_name = Regexp.escape(table.name)
-
90
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
-
90
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
-
end
-
-
3
def reverse_sql_order(order_query)
-
377
if order_query.empty?
-
3
return [table[primary_key].desc] if primary_key
-
3
raise IrreversibleOrderError,
-
"Relation has no current order and table has no primary key to be used as default order"
-
end
-
-
374
order_query.flat_map do |o|
-
395
case o
-
when Arel::Attribute
-
o.desc
-
when Arel::Nodes::Ordering
-
279
o.reverse
-
when String
-
116
if does_not_support_reverse?(o)
-
20
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
-
end
-
96
o.split(",").map! do |s|
-
120
s.strip!
-
120
s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
-
end
-
else
-
o
-
end
-
end
-
end
-
-
3
def does_not_support_reverse?(order)
-
# Account for String subclasses like Arel::Nodes::SqlLiteral that
-
# override methods like #count.
-
116
order = String.new(order) unless order.instance_of?(String)
-
-
# Uses SQL function with multiple arguments.
-
177
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
-
# Uses "nulls first" like construction.
-
/\bnulls\s+(?:first|last)\b/i.match?(order)
-
end
-
-
3
def build_order(arel)
-
41333
orders = order_values.compact_blank
-
41333
arel.order(*orders) unless orders.empty?
-
end
-
-
3
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
-
"asc", "desc", "ASC", "DESC"].to_set # :nodoc:
-
-
3
def validate_order_args(args)
-
13670
args.each do |arg|
-
13769
next unless arg.is_a?(Hash)
-
207
arg.each do |_key, value|
-
213
unless VALID_DIRECTIONS.include?(value)
-
6
raise ArgumentError,
-
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
-
end
-
end
-
end
-
end
-
-
3
def preprocess_order_args(order_args)
-
13684
@klass.disallow_raw_sql!(
-
13786
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
-
permit: connection.column_name_with_order_matcher
-
)
-
-
13670
validate_order_args(order_args)
-
-
13664
references = column_references(order_args)
-
13664
self.references_values |= references unless references.empty?
-
-
# if a symbol is given we prepend the quoted table name
-
order_args.map! do |arg|
-
13757
case arg
-
when Symbol
-
1327
order_column(arg.to_s).asc
-
when Hash
-
201
arg.map { |field, dir|
-
207
case field
-
when Arel::Nodes::SqlLiteral
-
24
field.send(dir.downcase)
-
else
-
183
order_column(field.to_s).send(dir.downcase)
-
end
-
}
-
else
-
12229
arg
-
end
-
13664
end.flatten!
-
end
-
-
3
def sanitize_order_arguments(order_args)
-
9369
order_args.map! do |arg|
-
9423
klass.sanitize_sql_for_order(arg)
-
end
-
9366
order_args.flatten!
-
9366
order_args.compact_blank!
-
end
-
-
3
def column_references(order_args)
-
13664
references = order_args.grep(String)
-
22330
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
-
13664
references
-
end
-
-
3
def order_column(field)
-
1510
arel_column(field) do |attr_name|
-
22
if attr_name == "count" && !group_values.empty?
-
1
table[attr_name]
-
else
-
21
Arel.sql(connection.quote_table_name(attr_name))
-
end
-
end
-
end
-
-
3
def resolve_arel_attributes(attrs)
-
330
attrs.flat_map do |attr|
-
342
case attr
-
when Arel::Predications
-
135
attr
-
when Hash
-
6
attr.flat_map do |table, columns|
-
6
table = table.to_s
-
6
Array(columns).map do |column|
-
6
predicate_builder.resolve_arel_attribute(table, column)
-
end
-
end
-
else
-
201
attr = attr.to_s
-
201
if attr.include?(".")
-
6
table, column = attr.split(".", 2)
-
6
predicate_builder.resolve_arel_attribute(table, column)
-
else
-
195
attr
-
end
-
end
-
end
-
end
-
-
# Checks to make sure that the arguments are not blank. Note that if some
-
# blank-like object were initially passed into the query method, then this
-
# method will not raise an error.
-
#
-
# Example:
-
#
-
# Post.references() # raises an error
-
# Post.references([]) # does not raise an error
-
#
-
# This particular method should be called with a method_name and the args
-
# passed into that method as an input. For example:
-
#
-
# def references(*args)
-
# check_if_method_has_arguments!("references", args)
-
# ...
-
# end
-
3
def check_if_method_has_arguments!(method_name, args, message = "The method .#{method_name}() must contain arguments.")
-
18277
if args.blank?
-
45
raise ArgumentError, message
-
18232
elsif block_given?
-
9384
yield args
-
else
-
8848
args.flatten!
-
8848
args.compact_blank!
-
end
-
end
-
-
3
STRUCTURAL_VALUE_METHODS = (
-
3
Relation::VALUE_METHODS -
-
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
-
).freeze # :nodoc:
-
-
3
def structurally_incompatible_values_for(other)
-
6173
values = other.values
-
6173
STRUCTURAL_VALUE_METHODS.reject do |method|
-
117287
v1, v2 = @values[method], values[method]
-
117287
if v1.is_a?(Array)
-
96
next true unless v2.is_a?(Array)
-
75
v1 = v1.uniq
-
75
v2 = v2.uniq
-
end
-
117266
v1 == v2 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
-
end
-
end
-
end
-
-
3
class Relation # :nodoc:
-
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
-
# TODO: Remove the class once Rails 6.1 has released.
-
3
class WhereClauseFactory # :nodoc:
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class Relation
-
3
module RecordFetchWarning
-
# When this module is prepended to ActiveRecord::Relation and
-
# +config.active_record.warn_on_records_fetched_greater_than+ is
-
# set to an integer, if the number of records a query returns is
-
# greater than the value of +warn_on_records_fetched_greater_than+,
-
# a warning is logged. This allows for the detection of queries that
-
# return a large number of records, which could cause memory bloat.
-
#
-
# In most cases, fetching large number of records can be performed
-
# efficiently using the ActiveRecord::Batches methods.
-
# See ActiveRecord::Batches for more information.
-
3
def exec_queries
-
24163
QueryRegistry.reset
-
-
24163
super.tap do |records|
-
24083
if logger && warn_on_records_fetched_greater_than
-
6
if records.length > warn_on_records_fetched_greater_than
-
3
logger.warn "Query fetched #{records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
-
end
-
end
-
end
-
end
-
-
# :stopdoc:
-
3
ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
-
426176
QueryRegistry.queries << payload[:sql]
-
end
-
# :startdoc:
-
-
3
class QueryRegistry # :nodoc:
-
3
extend ActiveSupport::PerThreadRegistry
-
-
3
attr_reader :queries
-
-
3
def initialize
-
116
@queries = []
-
end
-
-
3
def reset
-
24163
@queries.clear
-
end
-
end
-
end
-
end
-
end
-
-
3
ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/hash/except"
-
3
require "active_support/core_ext/hash/slice"
-
3
require "active_record/relation/merger"
-
-
3
module ActiveRecord
-
3
module SpawnMethods
-
# This is overridden by Associations::CollectionProxy
-
3
def spawn #:nodoc:
-
80780
already_in_scope? ? klass.all : clone
-
end
-
-
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
-
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
-
#
-
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
-
# # Performs a single join query with both where conditions.
-
#
-
# recent_posts = Post.order('created_at DESC').first(5)
-
# Post.where(published: true).merge(recent_posts)
-
# # Returns the intersection of all published posts with the 5 most recently created posts.
-
# # (This is just an example. You'd probably want to do this with a single query!)
-
#
-
# Procs will be evaluated by merge:
-
#
-
# Post.where(published: true).merge(-> { joins(:comments) })
-
# # => Post.where(published: true).joins(:comments)
-
#
-
# This is mainly intended for sharing common conditions between multiple associations.
-
3
def merge(other, *rest)
-
459
if other.is_a?(Array)
-
records & other
-
459
elsif other
-
453
spawn.merge!(other, *rest)
-
else
-
6
raise ArgumentError, "invalid argument: #{other.inspect}."
-
end
-
end
-
-
3
def merge!(other, *rest) # :nodoc:
-
39631
options = rest.extract_options!
-
39631
if other.is_a?(Hash)
-
753
Relation::HashMerger.new(self, other, options[:rewhere]).merge
-
38878
elsif other.is_a?(Relation)
-
38860
Relation::Merger.new(self, other, options[:rewhere]).merge
-
18
elsif other.respond_to?(:to_proc)
-
15
instance_exec(&other)
-
else
-
3
raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation"
-
end
-
end
-
-
# Removes from the query the condition(s) specified in +skips+.
-
#
-
# Post.order('id asc').except(:order) # discards the order condition
-
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
-
3
def except(*skips)
-
10279
relation_with values.except(*skips)
-
end
-
-
# Removes any condition from the query other than the one(s) specified in +onlies+.
-
#
-
# Post.order('id asc').only(:where) # discards the order condition
-
# Post.order('id asc').only(:where, :order) # uses the specified order
-
3
def only(*onlies)
-
12
relation_with values.slice(*onlies)
-
end
-
-
3
private
-
3
def relation_with(values)
-
10291
result = Relation.create(klass, values: values)
-
10291
result.extend(*extending_values) if extending_values.any?
-
10291
result
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/array/extract"
-
-
3
module ActiveRecord
-
3
class Relation
-
3
class WhereClause # :nodoc:
-
3
delegate :any?, :empty?, to: :predicates
-
-
3
def initialize(predicates)
-
233400
@predicates = predicates
-
end
-
-
3
def +(other)
-
59581
WhereClause.new(predicates + other.predicates)
-
end
-
-
3
def -(other)
-
36966
WhereClause.new(predicates - other.predicates)
-
end
-
-
3
def |(other)
-
54
WhereClause.new(predicates | other.predicates)
-
end
-
-
3
def merge(other, rewhere = nil)
-
79235
predicates = if rewhere
-
72
except_predicates(other.extract_attributes)
-
else
-
79163
predicates_unreferenced_by(other)
-
end
-
-
79235
WhereClause.new(predicates | other.predicates)
-
end
-
-
3
def except(*columns)
-
336
WhereClause.new(except_predicates(columns))
-
end
-
-
3
def or(other)
-
12322
left = self - other
-
12322
common = self - left
-
12322
right = other - common
-
-
12322
if left.empty? || right.empty?
-
6197
common
-
else
-
6125
left = left.ast
-
6125
left = left.expr if left.is_a?(Arel::Nodes::Grouping)
-
-
6125
right = right.ast
-
6125
right = right.expr if right.is_a?(Arel::Nodes::Grouping)
-
-
6125
or_clause = Arel::Nodes::Or.new(left, right)
-
-
6125
common.predicates << Arel::Nodes::Grouping.new(or_clause)
-
6125
common
-
end
-
end
-
-
3
def to_h(table_name = nil)
-
6734
equalities(predicates).each_with_object({}) do |node, hash|
-
7768
next if table_name&.!= node.left.relation.name
-
6724
name = node.left.name.to_s
-
6724
value = extract_node_value(node.right)
-
6724
hash[name] = value
-
end
-
end
-
-
3
def ast
-
44734
predicates = predicates_with_wrapped_sql_literals
-
44734
predicates.one? ? predicates.first : Arel::Nodes::And.new(predicates)
-
end
-
-
3
def ==(other)
-
246
other.is_a?(WhereClause) &&
-
predicates == other.predicates
-
end
-
-
3
def invert(as = :nand)
-
232
if predicates.size == 1
-
217
inverted_predicates = [ invert_predicate(predicates.first) ]
-
15
elsif as == :nor
-
54
inverted_predicates = predicates.map { |node| invert_predicate(node) }
-
else
-
6
inverted_predicates = [ Arel::Nodes::Not.new(ast) ]
-
end
-
-
229
WhereClause.new(inverted_predicates)
-
end
-
-
3
def self.empty
-
394691
@empty ||= new([]).freeze
-
end
-
-
3
def contradiction?
-
26088
predicates.any? do |x|
-
24517
case x
-
when Arel::Nodes::In
-
95
Array === x.right && x.right.empty?
-
when Arel::Nodes::Equality
-
19483
x.right.respond_to?(:unboundable?) && x.right.unboundable?
-
end
-
end
-
end
-
-
3
def extract_attributes
-
153
predicates.each_with_object([]) do |node, attrs|
-
159
attr = extract_attribute(node) || begin
-
15
node.left if node.equality? && node.left.is_a?(Arel::Predications)
-
end
-
159
attrs << attr if attr
-
end
-
end
-
-
3
protected
-
3
attr_reader :predicates
-
-
3
def referenced_columns
-
79163
predicates.each_with_object({}) do |node, hash|
-
22779
attr = extract_attribute(node) || begin
-
504
node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
-
end
-
-
22779
hash[attr] = node if attr
-
end
-
end
-
-
3
private
-
3
def extract_attribute(node)
-
28966
attr_node = nil
-
28966
Arel.fetch_attribute(node) do |attr|
-
28498
return if attr_node&.!= attr # all attr nodes should be the same
-
28480
attr_node = attr
-
end
-
28948
attr_node
-
end
-
-
3
def equalities(predicates)
-
6734
equalities = []
-
-
6734
predicates.each do |node|
-
8523
if equality_node?(node)
-
7768
equalities << node
-
755
elsif node.is_a?(Arel::Nodes::And)
-
equalities.concat equalities(node.children)
-
end
-
end
-
-
6734
equalities
-
end
-
-
3
def predicates_unreferenced_by(other)
-
79163
referenced_columns = other.referenced_columns
-
-
79163
predicates.reject do |node|
-
6028
attr = extract_attribute(node) || begin
-
30
node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
-
end
-
6028
next false unless attr
-
-
6001
ref = referenced_columns[attr]
-
6001
next false unless ref
-
-
2267
if equality_node?(node) && equality_node?(ref) || node == ref
-
2225
true
-
else
-
42
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Merging (#{node.to_sql}) and (#{ref.to_sql}) no longer maintain
-
both conditions, and will be replaced by the latter in Rails 6.2.
-
To migrate to Rails 6.2's behavior, use `relation.merge(other, rewhere: true)`.
-
MSG
-
42
false
-
end
-
end
-
end
-
-
3
def equality_node?(node)
-
13558
!node.is_a?(String) && node.equality?
-
end
-
-
3
def invert_predicate(node)
-
262
case node
-
when NilClass
-
3
raise ArgumentError, "Invalid argument for .where.not(), got nil."
-
when String
-
3
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
-
else
-
256
node.invert
-
end
-
end
-
-
3
def except_predicates(columns)
-
834
attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
-
630
non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
-
-
408
predicates.reject do |node|
-
414
if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
-
6
non_attrs.include?(node.left)
-
414
end || Arel.fetch_attribute(node) do |attr|
-
429
attrs.include?(attr) || columns.include?(attr.name.to_s)
-
end
-
end
-
end
-
-
3
def predicates_with_wrapped_sql_literals
-
44734
non_empty_predicates.map do |node|
-
57759
case node
-
when Arel::Nodes::SqlLiteral, ::String
-
1632
wrap_sql_literal(node)
-
56127
else node
-
end
-
end
-
end
-
-
3
ARRAY_WITH_EMPTY_STRING = [""]
-
3
def non_empty_predicates
-
44734
predicates - ARRAY_WITH_EMPTY_STRING
-
end
-
-
3
def wrap_sql_literal(node)
-
1632
if ::String === node
-
1632
node = Arel.sql(node)
-
end
-
1632
Arel::Nodes::Grouping.new(node)
-
end
-
-
3
def extract_node_value(node)
-
8025
case node
-
when Array
-
1644
node.map { |v| extract_node_value(v) }
-
when Arel::Nodes::BindParam, Arel::Nodes::Casted, Arel::Nodes::Quoted
-
7682
node.value_before_type_cast
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
###
-
# This class encapsulates a result returned from calling
-
# {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
-
# on any database connection adapter. For example:
-
#
-
# result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
-
# result # => #<ActiveRecord::Result:0xdeadbeef>
-
#
-
# # Get the column names of the result:
-
# result.columns
-
# # => ["id", "title", "body"]
-
#
-
# # Get the record values of the result:
-
# result.rows
-
# # => [[1, "title_1", "body_1"],
-
# [2, "title_2", "body_2"],
-
# ...
-
# ]
-
#
-
# # Get an array of hashes representing the result (column => value):
-
# result.to_a
-
# # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
-
# {"id" => 2, "title" => "title_2", "body" => "body_2"},
-
# ...
-
# ]
-
#
-
# # ActiveRecord::Result also includes Enumerable.
-
# result.each do |row|
-
# puts row['title'] + " " + row['body']
-
# end
-
3
class Result
-
3
include Enumerable
-
-
3
attr_reader :columns, :rows, :column_types
-
-
3
def initialize(columns, rows, column_types = {})
-
164391
@columns = columns
-
164391
@rows = rows
-
164391
@hash_rows = nil
-
164391
@column_types = column_types
-
end
-
-
# Returns true if this result set includes the column named +name+
-
3
def includes_column?(name)
-
29774
@columns.include? name
-
end
-
-
# Returns the number of elements in the rows array.
-
3
def length
-
30303
@rows.length
-
end
-
-
# Calls the given block once for each element in row collection, passing
-
# row as parameter.
-
#
-
# Returns an +Enumerator+ if no block is given.
-
3
def each
-
85791
if block_given?
-
634797
hash_rows.each { |row| yield row }
-
else
-
9
hash_rows.to_enum { @rows.size }
-
end
-
end
-
-
3
def to_hash
-
3
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`ActiveRecord::Result#to_hash` has been renamed to `to_a`.
-
`to_hash` is deprecated and will be removed in Rails 6.1.
-
MSG
-
3
to_a
-
end
-
-
3
alias :map! :map
-
3
alias :collect! :map
-
3
deprecate "map!": :map
-
3
deprecate "collect!": :map
-
-
# Returns true if there are no records, otherwise false.
-
3
def empty?
-
28844
rows.empty?
-
end
-
-
# Returns an array of hashes representing each row record.
-
3
def to_ary
-
23
hash_rows
-
end
-
-
3
alias :to_a :to_ary
-
-
3
def [](idx)
-
3
hash_rows[idx]
-
end
-
-
# Returns the last record from the rows collection.
-
3
def last(n = nil)
-
12
n ? hash_rows.last(n) : hash_rows.last
-
end
-
-
3
def cast_values(type_overrides = {}) # :nodoc:
-
6338
if columns.one?
-
# Separated to avoid allocating an array per row
-
-
5990
type = if type_overrides.is_a?(Array)
-
1839
type_overrides.first
-
else
-
4151
column_type(columns.first, type_overrides)
-
end
-
-
5990
rows.map do |(value)|
-
9409
type.deserialize(value)
-
end
-
else
-
348
types = if type_overrides.is_a?(Array)
-
65
type_overrides
-
else
-
872
columns.map { |name| column_type(name, type_overrides) }
-
end
-
-
348
rows.map do |values|
-
4585
Array.new(values.size) { |i| types[i].deserialize(values[i]) }
-
end
-
end
-
end
-
-
3
def initialize_copy(other)
-
398
@columns = columns.dup
-
398
@rows = rows.dup
-
398
@column_types = column_types.dup
-
398
@hash_rows = nil
-
end
-
-
3
private
-
3
def column_type(name, type_overrides = {})
-
4740
type_overrides.fetch(name) do
-
4427
column_types.fetch(name, Type.default_value)
-
end
-
end
-
-
3
def hash_rows
-
85829
@hash_rows ||=
-
begin
-
# We freeze the strings to prevent them getting duped when
-
# used as keys in ActiveRecord::Base's @attributes hash
-
85829
columns = @columns.map(&:-@)
-
85829
length = columns.length
-
85829
template = nil
-
-
85829
@rows.map { |row|
-
549218
if template
-
# We use transform_values to build subsequent rows from the
-
# hash of the first row. This is faster because we avoid any
-
# reallocs and in Ruby 2.7+ avoid hashing entirely.
-
477064
index = -1
-
477064
template.transform_values do
-
2566450
row[index += 1]
-
end
-
else
-
# In the past we used Hash[columns.zip(row)]
-
# though elegant, the verbose way is much more efficient
-
# both time and memory wise cause it avoids a big array allocation
-
# this method is called a lot and needs to be micro optimised
-
72154
hash = {}
-
-
72154
index = 0
-
72154
while index < length
-
498180
hash[columns[index]] = row[index]
-
498180
index += 1
-
end
-
-
# It's possible to select the same column twice, in which case
-
# we can't use a template
-
72154
template = hash if hash.length == length
-
-
72154
hash
-
end
-
}
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/per_thread_registry"
-
-
3
module ActiveRecord
-
# This is a thread locals registry for Active Record. For example:
-
#
-
# ActiveRecord::RuntimeRegistry.connection_handler
-
#
-
# returns the connection handler local to the current thread.
-
#
-
# See the documentation of ActiveSupport::PerThreadRegistry
-
# for further details.
-
3
class RuntimeRegistry # :nodoc:
-
3
extend ActiveSupport::PerThreadRegistry
-
-
3
attr_accessor :sql_runtime
-
-
3
[:sql_runtime].each do |val|
-
3
class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
-
3
class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Sanitization
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
# Accepts an array or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a WHERE clause.
-
#
-
# sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
-
# # => "name='foo''bar' and group_id=4"
-
#
-
# sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
-
# # => "name='foo''bar' and group_id='4'"
-
#
-
# sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
-
# # => "name='foo''bar' and group_id='4'"
-
#
-
# sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
-
# # => "name='foo''bar' and group_id='4'"
-
3
def sanitize_sql_for_conditions(condition)
-
32234
return nil if condition.blank?
-
-
32234
case condition
-
417
when Array; sanitize_sql_array(condition)
-
31817
else condition
-
end
-
end
-
3
alias :sanitize_sql :sanitize_sql_for_conditions
-
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a SET clause.
-
#
-
# sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
-
# # => "name=NULL and group_id=4"
-
#
-
# sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
-
# # => "name=NULL and group_id=4"
-
#
-
# Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
-
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
-
#
-
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
-
# # => "name=NULL and group_id='4'"
-
3
def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
-
66
case assignments
-
18
when Array; sanitize_sql_array(assignments)
-
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
-
48
else assignments
-
end
-
end
-
-
# Accepts an array, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for an ORDER clause.
-
#
-
# sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
-
# # => "field(id, 1,3,2)"
-
#
-
# sanitize_sql_for_order("id ASC")
-
# # => "id ASC"
-
3
def sanitize_sql_for_order(condition)
-
9420
if condition.is_a?(Array) && condition.first.to_s.include?("?")
-
21
disallow_raw_sql!(
-
[condition.first],
-
permit: connection.column_name_with_order_matcher
-
)
-
-
# Ensure we aren't dealing with a subclass of String that might
-
# override methods we use (e.g. Arel::Nodes::SqlLiteral).
-
18
if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
-
18
condition = [String.new(condition.first), *condition[1..-1]]
-
end
-
-
18
Arel.sql(sanitize_sql_array(condition))
-
else
-
9399
condition
-
end
-
end
-
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
-
#
-
# sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
-
# # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
-
3
def sanitize_sql_hash_for_assignment(attrs, table)
-
c = connection
-
attrs.map do |attr, value|
-
type = type_for_attribute(attr)
-
value = type.serialize(type.cast(value))
-
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
-
end.join(", ")
-
end
-
-
# Sanitizes a +string+ so that it is safe to use within an SQL
-
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
-
#
-
# sanitize_sql_like("100%")
-
# # => "100\\%"
-
#
-
# sanitize_sql_like("snake_cased_string")
-
# # => "snake\\_cased\\_string"
-
#
-
# sanitize_sql_like("100%", "!")
-
# # => "100!%"
-
#
-
# sanitize_sql_like("snake_cased_string", "!")
-
# # => "snake!_cased!_string"
-
3
def sanitize_sql_like(string, escape_character = "\\")
-
33
pattern = Regexp.union(escape_character, "%", "_")
-
84
string.gsub(pattern) { |x| [escape_character, x].join }
-
end
-
-
# Accepts an array of conditions. The array has each value
-
# sanitized and interpolated into the SQL statement.
-
#
-
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
-
# # => "name='foo''bar' and group_id=4"
-
#
-
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
-
# # => "name='foo''bar' and group_id=4"
-
#
-
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
-
# # => "name='foo''bar' and group_id='4'"
-
3
def sanitize_sql_array(ary)
-
498
statement, *values = ary
-
498
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
-
57
replace_named_bind_variables(statement, values.first)
-
441
elsif statement.include?("?")
-
399
replace_bind_variables(statement, values)
-
39
elsif statement.blank?
-
3
statement
-
else
-
72
statement % values.collect { |value| connection.quote_string(value.to_s) }
-
end
-
end
-
-
3
def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
-
15610
unexpected = nil
-
15610
args.each do |arg|
-
15794
next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s)
-
27
(unexpected ||= []) << arg
-
end
-
-
15610
return unless unexpected
-
-
27
if allow_unsafe_raw_sql == :deprecated
-
6
ActiveSupport::Deprecation.warn(
-
"Dangerous query method (method whose arguments are used as raw " \
-
"SQL) called with non-attribute argument(s): " \
-
"#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
-
"arguments will be disallowed in Rails 6.1. This method should " \
-
"not be called with user-provided values, such as request " \
-
"parameters or model attributes. Known-safe values can be passed " \
-
"by wrapping them in Arel.sql()."
-
)
-
else
-
21
raise(ActiveRecord::UnknownAttributeReference,
-
"Query method called with non-attribute argument(s): " +
-
unexpected.map(&:inspect).join(", ")
-
)
-
end
-
end
-
-
3
private
-
3
def replace_bind_variables(statement, values)
-
465
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
-
450
bound = values.dup
-
450
c = connection
-
450
statement.gsub(/\?/) do
-
459
replace_bind_variable(bound.shift, c)
-
end
-
end
-
-
3
def replace_bind_variable(value, c = connection)
-
555
if ActiveRecord::Relation === value
-
18
value.to_sql
-
else
-
537
quote_bound_value(value, c)
-
end
-
end
-
-
3
def replace_named_bind_variables(statement, bind_vars)
-
93
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
-
111
if $1 == ":" # skip postgresql casts
-
12
match # return the whole match
-
99
elsif bind_vars.include?(match = $2.to_sym)
-
96
replace_bind_variable(bind_vars[match])
-
else
-
3
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
-
end
-
end
-
end
-
-
3
def quote_bound_value(value, c = connection)
-
537
if value.respond_to?(:map) && !value.acts_like?(:string)
-
186
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
-
69
if values.empty?
-
24
c.quote(nil)
-
else
-
162
values.map! { |v| c.quote(v) }.join(",")
-
end
-
else
-
468
value = value.id_for_database if value.respond_to?(:id_for_database)
-
468
c.quote(value)
-
end
-
end
-
-
3
def raise_if_bind_arity_mismatch(statement, expected, provided)
-
465
unless expected == provided
-
15
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record \Schema
-
#
-
# Allows programmers to programmatically define a schema in a portable
-
# DSL. This means you can define tables, indexes, etc. without using SQL
-
# directly, so your applications can more easily support multiple
-
# databases.
-
#
-
# Usage:
-
#
-
# ActiveRecord::Schema.define do
-
# create_table :authors do |t|
-
# t.string :name, null: false
-
# end
-
#
-
# add_index :authors, :name, :unique
-
#
-
# create_table :posts do |t|
-
# t.integer :author_id, null: false
-
# t.string :subject
-
# t.text :body
-
# t.boolean :private, default: false
-
# end
-
#
-
# add_index :posts, :author_id
-
# end
-
#
-
# ActiveRecord::Schema is only supported by database adapters that also
-
# support migrations, the two features being very similar.
-
3
class Schema < Migration::Current
-
# Eval the given block. All methods available to the current connection
-
# adapter are available within the block, so you can easily use the
-
# database definition DSL to build up your schema (
-
# {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
-
# {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
-
#
-
# The +info+ hash is optional, and if given is used to define metadata
-
# about the current schema (currently, only the schema's version):
-
#
-
# ActiveRecord::Schema.define(version: 2038_01_19_000001) do
-
# ...
-
# end
-
3
def self.define(info = {}, &block)
-
52
new.define(info, &block)
-
end
-
-
3
def define(info, &block) # :nodoc:
-
52
instance_eval(&block)
-
-
49
if info[:version].present?
-
12
connection.schema_migration.create_table
-
12
connection.assume_migrated_upto_version(info[:version])
-
end
-
-
49
ActiveRecord::InternalMetadata.create_table
-
49
ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "stringio"
-
-
3
module ActiveRecord
-
# = Active Record Schema Dumper
-
#
-
# This class is used to dump the database schema for some connection to some
-
# output format (i.e., ActiveRecord::Schema).
-
3
class SchemaDumper #:nodoc:
-
3
private_class_method :new
-
-
##
-
# :singleton-method:
-
# A list of tables which should not be dumped to the schema.
-
# Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby.
-
# Only strings are accepted if ActiveRecord::Base.schema_format == :sql.
-
3
cattr_accessor :ignore_tables, default: []
-
-
##
-
# :singleton-method:
-
# Specify a custom regular expression matching foreign keys which name
-
# should not be dumped to db/schema.rb.
-
3
cattr_accessor :fk_ignore_pattern, default: /^fk_rails_[0-9a-f]{10}$/
-
-
##
-
# :singleton-method:
-
# Specify a custom regular expression matching check constraints which name
-
# should not be dumped to db/schema.rb.
-
3
cattr_accessor :chk_ignore_pattern, default: /^chk_rails_[0-9a-f]{10}$/
-
-
3
class << self
-
3
def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
-
197
connection.create_schema_dumper(generate_options(config)).dump(stream)
-
197
stream
-
end
-
-
3
private
-
3
def generate_options(config)
-
197
{
-
table_name_prefix: config.table_name_prefix,
-
table_name_suffix: config.table_name_suffix
-
}
-
end
-
end
-
-
3
def dump(stream)
-
197
header(stream)
-
197
extensions(stream)
-
197
tables(stream)
-
197
trailer(stream)
-
197
stream
-
end
-
-
3
private
-
3
attr_accessor :table_name
-
-
3
def initialize(connection, options = {})
-
197
@connection = connection
-
197
@version = connection.migration_context.current_version rescue nil
-
197
@options = options
-
end
-
-
# turns 20170404131909 into "2017_04_04_131909"
-
3
def formatted_version
-
197
stringified = @version.to_s
-
197
return stringified unless stringified.length == 14
-
stringified.insert(4, "_").insert(7, "_").insert(10, "_")
-
end
-
-
3
def define_params
-
197
@version ? "version: #{formatted_version}" : ""
-
end
-
-
3
def header(stream)
-
197
stream.puts <<HEADER
-
# This file is auto-generated from the current state of the database. Instead
-
# of editing this file, please use the migrations feature of Active Record to
-
# incrementally modify your database, and then regenerate this schema definition.
-
#
-
# This file is the source Rails uses to define your schema when running `bin/rails
-
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
-
# be faster and is potentially less error prone than running all of your
-
# migrations from scratch. Old migrations may fail to apply correctly if those
-
# migrations use external dependencies or application code.
-
#
-
# It's strongly recommended that you check this file into your version control system.
-
-
ActiveRecord::Schema.define(#{define_params}) do
-
-
HEADER
-
end
-
-
3
def trailer(stream)
-
197
stream.puts "end"
-
end
-
-
# extensions are only supported by PostgreSQL
-
3
def extensions(stream)
-
end
-
-
3
def tables(stream)
-
197
sorted_tables = @connection.tables.sort
-
-
197
sorted_tables.each do |table_name|
-
39252
table(table_name, stream) unless ignored?(table_name)
-
end
-
-
# dump foreign keys at the end to make sure all dependent tables exist.
-
197
if @connection.supports_foreign_keys?
-
197
sorted_tables.each do |tbl|
-
39252
foreign_keys(tbl, stream) unless ignored?(tbl)
-
end
-
end
-
end
-
-
3
def table(table, stream)
-
4499
columns = @connection.columns(table)
-
4499
begin
-
4499
self.table_name = table
-
-
4499
tbl = StringIO.new
-
-
# first dump primary key column
-
4499
pk = @connection.primary_key(table)
-
-
4499
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
-
-
4499
case pk
-
when String
-
4020
tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
-
8044
pkcol = columns.detect { |c| c.name == pk }
-
4020
pkcolspec = column_spec_for_primary_key(pkcol)
-
4020
unless pkcolspec.empty?
-
262
if pkcolspec != pkcolspec.slice(:id, :default)
-
4
pkcolspec = { id: { type: pkcolspec.delete(:id), **pkcolspec }.compact }
-
end
-
262
tbl.print ", #{format_colspec(pkcolspec)}"
-
end
-
when Array
-
28
tbl.print ", primary_key: #{pk.inspect}"
-
else
-
451
tbl.print ", id: false"
-
end
-
-
4499
table_options = @connection.table_options(table)
-
4499
if table_options.present?
-
2
tbl.print ", #{format_options(table_options)}"
-
end
-
-
4499
tbl.puts ", force: :cascade do |t|"
-
-
# then dump all non-primary key columns
-
4499
columns.each do |column|
-
16929
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
-
16929
next if column.name == pk
-
12909
type, colspec = column_spec(column)
-
12909
if type.is_a?(Symbol)
-
12909
tbl.print " t.#{type} #{column.name.inspect}"
-
else
-
tbl.print " t.column #{column.name.inspect}, #{type.inspect}"
-
end
-
12909
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
-
12909
tbl.puts
-
end
-
-
4499
indexes_in_create(table, tbl)
-
4499
check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
-
-
4499
tbl.puts " end"
-
4499
tbl.puts
-
-
4499
tbl.rewind
-
4499
stream.print tbl.read
-
rescue => e
-
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
-
stream.puts "# #{e.message}"
-
stream.puts
-
ensure
-
4499
self.table_name = nil
-
end
-
end
-
-
# Keep it for indexing materialized views
-
3
def indexes(table, stream)
-
if (indexes = @connection.indexes(table)).any?
-
add_index_statements = indexes.map do |index|
-
table_name = remove_prefix_and_suffix(index.table).inspect
-
" add_index #{([table_name] + index_parts(index)).join(', ')}"
-
end
-
-
stream.puts add_index_statements.sort.join("\n")
-
stream.puts
-
end
-
end
-
-
3
def indexes_in_create(table, stream)
-
4499
if (indexes = @connection.indexes(table)).any?
-
812
index_statements = indexes.map do |index|
-
1501
" t.index #{index_parts(index).join(', ')}"
-
end
-
812
stream.puts index_statements.sort.join("\n")
-
end
-
end
-
-
3
def index_parts(index)
-
1501
index_parts = [
-
index.columns.inspect,
-
"name: #{index.name.inspect}",
-
]
-
1501
index_parts << "unique: true" if index.unique
-
1501
index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present?
-
1501
index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present?
-
1501
index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
-
1501
index_parts << "where: #{index.where.inspect}" if index.where
-
1501
index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
-
1501
index_parts << "type: #{index.type.inspect}" if index.type
-
1501
index_parts << "comment: #{index.comment.inspect}" if index.comment
-
1501
index_parts
-
end
-
-
3
def check_constraints_in_create(table, stream)
-
4499
if (check_constraints = @connection.check_constraints(table)).any?
-
25
add_check_constraint_statements = check_constraints.map do |check_constraint|
-
25
parts = [
-
"t.check_constraint #{check_constraint.expression.inspect}"
-
]
-
-
25
if check_constraint.export_name_on_schema_dump?
-
25
parts << "name: #{check_constraint.name.inspect}"
-
end
-
-
25
" #{parts.join(', ')}"
-
end
-
-
25
stream.puts add_check_constraint_statements.sort.join("\n")
-
end
-
end
-
-
3
def foreign_keys(table, stream)
-
4499
if (foreign_keys = @connection.foreign_keys(table)).any?
-
160
add_foreign_key_statements = foreign_keys.map do |foreign_key|
-
226
parts = [
-
"add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
-
remove_prefix_and_suffix(foreign_key.to_table).inspect,
-
]
-
-
226
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
-
32
parts << "column: #{foreign_key.column.inspect}"
-
end
-
-
226
if foreign_key.custom_primary_key?
-
25
parts << "primary_key: #{foreign_key.primary_key.inspect}"
-
end
-
-
226
if foreign_key.export_name_on_schema_dump?
-
11
parts << "name: #{foreign_key.name.inspect}"
-
end
-
-
226
parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
-
226
parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
-
-
226
" #{parts.join(', ')}"
-
end
-
-
160
stream.puts add_foreign_key_statements.sort.join("\n")
-
end
-
end
-
-
3
def format_colspec(colspec)
-
colspec.map do |key, value|
-
5032
"#{key}: #{ value.is_a?(Hash) ? "{ #{format_colspec(value)} }" : value }"
-
4111
end.join(", ")
-
end
-
-
3
def format_options(options)
-
86
options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
-
end
-
-
3
def format_index_parts(options)
-
80
if options.is_a?(Hash)
-
41
"{ #{format_options(options)} }"
-
else
-
39
options.inspect
-
end
-
end
-
-
3
def remove_prefix_and_suffix(table)
-
6777179
prefix = Regexp.escape(@options[:table_name_prefix].to_s)
-
6777179
suffix = Regexp.escape(@options[:table_name_suffix].to_s)
-
6777179
table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1")
-
end
-
-
3
def ignored?(table_name)
-
78504
[ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored|
-
6772228
ignored === remove_prefix_and_suffix(table_name)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/scoping/default"
-
3
require "active_record/scoping/named"
-
-
3
module ActiveRecord
-
# This class is used to create a table that keeps track of which migrations
-
# have been applied to a given database. When a migration is run, its schema
-
# number is inserted in to the `SchemaMigration.table_name` so it doesn't need
-
# to be executed the next time.
-
3
class SchemaMigration < ActiveRecord::Base # :nodoc:
-
3
class << self
-
3
def _internal?
-
true
-
end
-
-
3
def primary_key
-
1658
"version"
-
end
-
-
3
def table_name
-
1536
"#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
-
end
-
-
3
def create_table
-
780
unless table_exists?
-
38
version_options = connection.internal_string_options_for_primary_key
-
-
38
connection.create_table(table_name, id: false) do |t|
-
38
t.string :version, **version_options
-
end
-
end
-
end
-
-
3
def drop_table
-
57
connection.drop_table table_name, if_exists: true
-
end
-
-
3
def normalize_migration_number(number)
-
129
"%.3d" % number.to_i
-
end
-
-
3
def normalized_versions
-
55
all_versions.map { |v| normalize_migration_number v }
-
end
-
-
3
def all_versions
-
801
order(:version).pluck(:version)
-
end
-
end
-
-
3
def version
-
3
super.to_i
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/per_thread_registry"
-
-
3
module ActiveRecord
-
3
module Scoping
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
3
include Default
-
3
include Named
-
end
-
-
3
module ClassMethods # :nodoc:
-
# Collects attributes from scopes that should be applied when creating
-
# an AR instance for the particular class this is called on.
-
3
def scope_attributes
-
905
all.scope_for_create
-
end
-
-
# Are there attributes associated with this scope?
-
3
def scope_attributes?
-
33849
current_scope
-
end
-
-
3
def current_scope(skip_inherited_scope = false)
-
196676
ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
-
end
-
-
3
def current_scope=(scope)
-
148554
ScopeRegistry.set_value_for(:current_scope, self, scope)
-
end
-
end
-
-
3
def populate_with_current_scope_attributes # :nodoc:
-
15758
return unless self.class.scope_attributes?
-
-
887
attributes = self.class.scope_attributes
-
887
_assign_attributes(attributes) if attributes.any?
-
end
-
-
3
def initialize_internals_callback # :nodoc:
-
15758
super
-
15758
populate_with_current_scope_attributes
-
end
-
-
# This class stores the +:current_scope+ and +:ignore_default_scope+ values
-
# for different classes. The registry is stored as a thread local, which is
-
# accessed through +ScopeRegistry.current+.
-
#
-
# This class allows you to store and get the scope values on different
-
# classes and different types of scopes. For example, if you are attempting
-
# to get the current_scope for the +Board+ model, then you would use the
-
# following code:
-
#
-
# registry = ActiveRecord::Scoping::ScopeRegistry
-
# registry.set_value_for(:current_scope, Board, some_new_scope)
-
#
-
# Now when you run:
-
#
-
# registry.value_for(:current_scope, Board)
-
#
-
# You will obtain whatever was defined in +some_new_scope+. The #value_for
-
# and #set_value_for methods are delegated to the current ScopeRegistry
-
# object, so the above example code can also be called as:
-
#
-
# ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
-
# Board, some_new_scope)
-
3
class ScopeRegistry # :nodoc:
-
3
extend ActiveSupport::PerThreadRegistry
-
-
3
VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
-
-
3
def initialize
-
107
@registry = Hash.new { |hash, key| hash[key] = {} }
-
end
-
-
# Obtains the value for a given +scope_type+ and +model+.
-
3
def value_for(scope_type, model, skip_inherited_scope = false)
-
208400
raise_invalid_scope_type!(scope_type)
-
208400
return @registry[scope_type][model.name] if skip_inherited_scope
-
132732
klass = model
-
132732
base = model.base_class
-
132732
while klass <= base
-
144780
value = @registry[scope_type][klass.name]
-
144780
return value if value
-
119068
klass = klass.superclass
-
end
-
end
-
-
# Sets the +value+ for a given +scope_type+ and +model+.
-
3
def set_value_for(scope_type, model, value)
-
157294
raise_invalid_scope_type!(scope_type)
-
157294
@registry[scope_type][model.name] = value
-
end
-
-
3
private
-
3
def raise_invalid_scope_type!(scope_type)
-
365694
if !VALID_SCOPE_TYPES.include?(scope_type)
-
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Scoping
-
3
module Default
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
# Stores the default scope for the class.
-
3
class_attribute :default_scopes, instance_writer: false, instance_predicate: false, default: []
-
3
class_attribute :default_scope_override, instance_writer: false, instance_predicate: false, default: nil
-
end
-
-
3
module ClassMethods
-
# Returns a scope for the model without the previously set scopes.
-
#
-
# class Post < ActiveRecord::Base
-
# def self.default_scope
-
# where(published: true)
-
# end
-
# end
-
#
-
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
-
# Post.unscoped.all # Fires "SELECT * FROM posts"
-
# Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
-
#
-
# This method also accepts a block. All queries inside the block will
-
# not use the previously set scopes.
-
#
-
# Post.unscoped {
-
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
-
# }
-
3
def unscoped
-
44948
block_given? ? relation.scoping { yield } : relation
-
end
-
-
# Are there attributes associated with this scope?
-
3
def scope_attributes? # :nodoc:
-
33849
super || default_scopes.any? || respond_to?(:default_scope)
-
end
-
-
3
def before_remove_const #:nodoc:
-
6
self.current_scope = nil
-
end
-
-
3
private
-
# Use this macro in your model to set a default scope for all operations on
-
# the model.
-
#
-
# class Article < ActiveRecord::Base
-
# default_scope { where(published: true) }
-
# end
-
#
-
# Article.all # => SELECT * FROM articles WHERE published = true
-
#
-
# The #default_scope is also applied while creating/building a record.
-
# It is not applied while updating a record.
-
#
-
# Article.new.published # => true
-
# Article.create.published # => true
-
#
-
# (You can also pass any object which responds to +call+ to the
-
# +default_scope+ macro, and it will be called when building the
-
# default scope.)
-
#
-
# If you use multiple #default_scope declarations in your model then
-
# they will be merged together:
-
#
-
# class Article < ActiveRecord::Base
-
# default_scope { where(published: true) }
-
# default_scope { where(rating: 'G') }
-
# end
-
#
-
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
-
#
-
# This is also the case with inheritance and module includes where the
-
# parent or module defines a #default_scope and the child or including
-
# class defines a second one.
-
#
-
# If you need to do more complex things with a default scope, you can
-
# alternatively define it as a class method:
-
#
-
# class Article < ActiveRecord::Base
-
# def self.default_scope
-
# # Should return a scope, you can call 'super' here etc.
-
# end
-
# end
-
3
def default_scope(scope = nil, &block) # :doc:
-
141
scope = block if block_given?
-
-
141
if scope.is_a?(Relation) || !scope.respond_to?(:call)
-
3
raise ArgumentError,
-
"Support for calling #default_scope without a block is removed. For example instead " \
-
"of `default_scope where(color: 'red')`, please use " \
-
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
-
"self.default_scope.)"
-
end
-
-
138
self.default_scopes += [scope]
-
end
-
-
3
def build_default_scope(relation = relation())
-
54778
return if abstract_class?
-
-
54775
if default_scope_override.nil?
-
1212
self.default_scope_override = !Base.is_a?(method(:default_scope).owner)
-
end
-
-
54775
if default_scope_override
-
# The user has defined their own default scope method, so call that
-
98
evaluate_default_scope do
-
49
if scope = default_scope
-
49
relation.merge!(scope)
-
end
-
end
-
54677
elsif default_scopes.any?
-
4336
evaluate_default_scope do
-
4321
default_scopes.inject(relation) do |default_scope, scope|
-
4504
scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
-
4504
default_scope.instance_exec(&scope) || default_scope
-
end
-
end
-
end
-
end
-
-
3
def ignore_default_scope?
-
11718
ScopeRegistry.value_for(:ignore_default_scope, base_class)
-
end
-
-
3
def ignore_default_scope=(ignore)
-
8740
ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
-
end
-
-
# The ignore_default_scope flag is used to prevent an infinite recursion
-
# situation where a default scope references a scope which has a default
-
# scope which references a scope...
-
3
def evaluate_default_scope
-
4434
return if ignore_default_scope?
-
-
4370
begin
-
4370
self.ignore_default_scope = true
-
4370
yield
-
ensure
-
4370
self.ignore_default_scope = false
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record \Named \Scopes
-
3
module Scoping
-
3
module Named
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
# Returns an ActiveRecord::Relation scope object.
-
#
-
# posts = Post.all
-
# posts.size # Fires "select count(*) from posts" and returns the count
-
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
-
#
-
# fruits = Fruit.all
-
# fruits = fruits.where(color: 'red') if options[:red_only]
-
# fruits = fruits.limit(10) if limited?
-
#
-
# You can define a scope that applies to all finders using
-
# {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope].
-
3
def all
-
35786
scope = current_scope
-
-
35786
if scope
-
13134
if scope._deprecated_scope_source
-
30
ActiveSupport::Deprecation.warn(<<~MSG.squish)
-
Class level methods will no longer inherit scoping from `#{scope._deprecated_scope_source}`
-
in Rails 6.1. To continue using the scoped relation, pass it into the block directly.
-
To instead access the full set of models, as Rails 6.1 will, use `#{name}.default_scoped`.
-
MSG
-
end
-
-
13134
if self == scope.klass
-
13104
scope.clone
-
else
-
30
relation.merge!(scope)
-
end
-
else
-
22652
default_scoped
-
end
-
end
-
-
3
def scope_for_association(scope = relation) # :nodoc:
-
32393
if current_scope&.empty_scope?
-
270
scope
-
else
-
32123
default_scoped(scope)
-
end
-
end
-
-
# Returns a scope for the model with default scopes.
-
3
def default_scoped(scope = relation)
-
54778
build_default_scope(scope) || scope
-
end
-
-
3
def default_extensions # :nodoc:
-
6730
if scope = scope_for_association || build_default_scope
-
6730
scope.extensions
-
else
-
[]
-
end
-
end
-
-
# Adds a class method for retrieving and querying objects.
-
# The method is intended to return an ActiveRecord::Relation
-
# object, which is composable with other scopes.
-
# If it returns +nil+ or +false+, an
-
# {all}[rdoc-ref:Scoping::Named::ClassMethods#all] scope is returned instead.
-
#
-
# A \scope represents a narrowing of a database query, such as
-
# <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') }
-
# scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
-
# end
-
#
-
# The above calls to #scope define class methods <tt>Shirt.red</tt> and
-
# <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
-
# represents the query <tt>Shirt.where(color: 'red')</tt>.
-
#
-
# Note that this is simply 'syntactic sugar' for defining an actual
-
# class method:
-
#
-
# class Shirt < ActiveRecord::Base
-
# def self.red
-
# where(color: 'red')
-
# end
-
# end
-
#
-
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
-
# <tt>Shirt.red</tt> is not an Array but an ActiveRecord::Relation,
-
# which is composable with other scopes; it resembles the association object
-
# constructed by a {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
-
# declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
-
# <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
-
# association objects, named \scopes act like an Array, implementing
-
# Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
-
# and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
-
# <tt>Shirt.red</tt> really was an array.
-
#
-
# These named \scopes are composable. For instance,
-
# <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
-
# both red and dry clean only. Nested finds and calculations also work
-
# with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
-
# returns the number of garments for which these criteria obtain.
-
# Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
-
#
-
# All scopes are available as class methods on the ActiveRecord::Base
-
# descendant upon which the \scopes were defined. But they are also
-
# available to {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
-
# associations. If,
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :shirts
-
# end
-
#
-
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
-
# Elton's red, dry clean only shirts.
-
#
-
# \Named scopes can also have extensions, just as with
-
# {has_many}[rdoc-ref:Associations::ClassMethods#has_many] declarations:
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') } do
-
# def dom_id
-
# 'red_shirts'
-
# end
-
# end
-
# end
-
#
-
# Scopes can also be used while creating/building a record.
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# end
-
#
-
# Article.published.new.published # => true
-
# Article.published.create.published # => true
-
#
-
# \Class methods on your model are automatically available
-
# on scopes. Assuming the following setup:
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# scope :featured, -> { where(featured: true) }
-
#
-
# def self.latest_article
-
# order('published_at desc').first
-
# end
-
#
-
# def self.titles
-
# pluck(:title)
-
# end
-
# end
-
#
-
# We are able to call the methods like this:
-
#
-
# Article.published.featured.latest_article
-
# Article.featured.titles
-
3
def scope(name, body, &block)
-
798
unless body.respond_to?(:call)
-
3
raise ArgumentError, "The scope body needs to be callable."
-
end
-
-
795
if dangerous_class_method?(name)
-
60
raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
-
"on the model \"#{self.name}\", but Active Record already defined " \
-
"a class method with the same name."
-
end
-
-
735
if method_defined_within?(name, Relation)
-
12
raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
-
"on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \
-
"an instance method with the same name."
-
end
-
-
723
valid_scope_name?(name)
-
723
extension = Module.new(&block) if block
-
-
723
if body.respond_to?(:to_proc)
-
720
singleton_class.define_method(name) do |*args|
-
798
scope = all._exec_scope(name, *args, &body)
-
798
scope = scope.extending(extension) if extension
-
798
scope
-
end
-
else
-
3
singleton_class.define_method(name) do |*args|
-
3
scope = body.call(*args) || all
-
3
scope = scope.extending(extension) if extension
-
3
scope
-
end
-
end
-
723
singleton_class.send(:ruby2_keywords, name) if respond_to?(:ruby2_keywords, true)
-
-
723
generate_relation_method(name)
-
end
-
-
3
private
-
3
def singleton_method_added(name)
-
163518
generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
-
end
-
-
3
def valid_scope_name?(name)
-
723
if respond_to?(name, true) && logger
-
51
logger.warn "Creating scope :#{name}. " \
-
"Overwriting existing method #{self.name}.#{name}."
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module SecureToken
-
3
class MinimumLengthError < StandardError; end
-
-
3
MINIMUM_TOKEN_LENGTH = 24
-
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
# Example using #has_secure_token
-
#
-
# # Schema: User(token:string, auth_token:string)
-
# class User < ActiveRecord::Base
-
# has_secure_token
-
# has_secure_token :auth_token, length: 36
-
# end
-
#
-
# user = User.new
-
# user.save
-
# user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
-
# user.auth_token # => "tU9bLuZseefXQ4yQxQo8wjtBvsAfPc78os6R"
-
# user.regenerate_token # => true
-
# user.regenerate_auth_token # => true
-
#
-
# <tt>SecureRandom::base58</tt> is used to generate at minimum a 24-character unique token, so collisions are highly unlikely.
-
#
-
# Note that it's still possible to generate a race condition in the database in the same way that
-
# {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
-
# You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
-
3
def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH)
-
9
if length < MINIMUM_TOKEN_LENGTH
-
3
raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
-
end
-
-
# Load securerandom only when has_secure_token is used.
-
6
require "active_support/core_ext/securerandom"
-
12
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
-
90
before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }
-
end
-
-
3
def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
-
84
SecureRandom.base58(length)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord #:nodoc:
-
# = Active Record \Serialization
-
3
module Serialization
-
3
extend ActiveSupport::Concern
-
3
include ActiveModel::Serializers::JSON
-
-
3
included do
-
3
self.include_root_in_json = false
-
end
-
-
3
def serializable_hash(options = nil)
-
252
if self.class._has_attribute?(self.class.inheritance_column)
-
159
options = options ? options.dup : {}
-
-
159
options[:except] = Array(options[:except]).map(&:to_s)
-
159
options[:except] |= Array(self.class.inheritance_column)
-
end
-
-
252
super(options)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record Signed Id
-
3
module SignedId
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
##
-
# :singleton-method:
-
# Set the secret used for the signed id verifier instance when using Active Record outside of Rails.
-
# Within Rails, this is automatically set using the Rails application key generator.
-
3
mattr_accessor :signed_id_verifier_secret, instance_writer: false
-
end
-
-
3
module ClassMethods
-
# Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
-
# This is particularly useful for things like password reset or email verification, where you want
-
# the bearer of the signed id to be able to interact with the underlying record, but usually only within
-
# a certain time period.
-
#
-
# You set the time period that the signed id is valid for during generation, using the instance method
-
# +signed_id(expires_in: 15.minutes)+. If the time has elapsed before a signed find is attempted,
-
# the signed id will no longer be valid, and nil is returned.
-
#
-
# It's possible to further restrict the use of a signed id with a purpose. This helps when you have a
-
# general base model, like a User, which might have signed ids for several things, like password reset
-
# or email verification. The purpose that was set during generation must match the purpose set when
-
# finding. If there's a mismatch, nil is again returned.
-
#
-
# ==== Examples
-
#
-
# signed_id = User.first.signed_id expires_in: 15.minutes, purpose: :password_reset
-
#
-
# User.find_signed signed_id # => nil, since the purpose does not match
-
#
-
# travel 16.minutes
-
# User.find_signed signed_id, purpose: :password_reset # => nil, since the signed id has expired
-
#
-
# travel_back
-
# User.find_signed signed_id, purpose: :password_reset # => User.first
-
3
def find_signed(signed_id, purpose: nil)
-
24
raise UnknownPrimaryKey.new(self) if primary_key.nil?
-
-
21
if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
-
15
find_by primary_key => id
-
end
-
end
-
-
# Works like +find_signed+, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
-
# exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
-
# or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
-
# the valid signed id can't find a record.
-
#
-
# === Examples
-
#
-
# User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
-
#
-
# signed_id = User.first.signed_id
-
# User.first.destroy
-
# User.find_signed! signed_id # => ActiveRecord::RecordNotFound
-
3
def find_signed!(signed_id, purpose: nil)
-
12
if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose))
-
6
find(id)
-
end
-
end
-
-
# The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
-
# with the class-level +signed_id_verifier_secret+, which within Rails comes from the
-
# Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
-
3
def signed_id_verifier
-
75
@signed_id_verifier ||= begin
-
17
secret = signed_id_verifier_secret
-
17
secret = secret.call if secret.respond_to?(:call)
-
-
17
if secret.nil?
-
6
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
-
else
-
11
ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON
-
end
-
end
-
end
-
-
# Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
-
# verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
-
# your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
-
3
def signed_id_verifier=(verifier)
-
6
@signed_id_verifier = verifier
-
end
-
-
# :nodoc:
-
3
def combine_signed_id_purposes(purpose)
-
60
[ name.underscore, purpose.to_s ].compact_blank.join("/")
-
end
-
end
-
-
-
# Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
-
# This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
-
# It can further more be set to expire (the default is not to expire), and scoped down with a specific purpose.
-
# If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
-
# record. If a purpose is set, this too must match.
-
#
-
# If you accidentally let a signed id out in the wild that you wish to retract sooner than its expiration date
-
# (or maybe you forgot to set an expiration date while meaning to!), you can use the purpose to essentially
-
# version the signed_id, like so:
-
#
-
# user.signed_id purpose: :v2
-
#
-
# And you then change your +find_signed+ calls to require this new purpose. Any old signed ids that were not
-
# created with the purpose will no longer find the record.
-
3
def signed_id(expires_in: nil, purpose: nil)
-
33
self.class.signed_id_verifier.generate id, expires_in: expires_in, purpose: self.class.combine_signed_id_purposes(purpose)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# Statement cache is used to cache a single statement in order to avoid creating the AST again.
-
# Initializing the cache is done by passing the statement in the create block:
-
#
-
# cache = StatementCache.create(Book.connection) do |params|
-
# Book.where(name: "my book").where("author_id > 3")
-
# end
-
#
-
# The cached statement is executed by using the
-
# {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
-
#
-
# cache.execute([], Book.connection)
-
#
-
# The relation returned by the block is cached, and for each
-
# {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
-
# call the cached relation gets duped. Database is queried when +to_a+ is called on the relation.
-
#
-
# If you want to cache the statement without the values you can use the +bind+ method of the
-
# block parameter.
-
#
-
# cache = StatementCache.create(Book.connection) do |params|
-
# Book.where(name: params.bind)
-
# end
-
#
-
# And pass the bind values as the first argument of +execute+ call.
-
#
-
# cache.execute(["my book"], Book.connection)
-
3
class StatementCache # :nodoc:
-
3
class Substitute; end # :nodoc:
-
-
3
class Query # :nodoc:
-
3
def initialize(sql)
-
1262
@sql = sql
-
end
-
-
3
def sql_for(binds, connection)
-
6171
@sql
-
end
-
end
-
-
3
class PartialQuery < Query # :nodoc:
-
3
def initialize(values)
-
15
@values = values
-
15
@indexes = values.each_with_index.find_all { |thing, i|
-
309
Substitute === thing
-
}.map(&:last)
-
end
-
-
3
def sql_for(binds, connection)
-
15
val = @values.dup
-
15
@indexes.each do |i|
-
48
value = binds.shift
-
48
if ActiveModel::Attribute === value
-
24
value = value.value_for_database
-
end
-
48
val[i] = connection.quote(value)
-
end
-
15
val.join
-
end
-
end
-
-
3
class PartialQueryCollector
-
3
attr_accessor :preparable
-
-
3
def initialize
-
15
@parts = []
-
15
@binds = []
-
end
-
-
3
def <<(str)
-
243
@parts << str
-
243
self
-
end
-
-
3
def add_bind(obj)
-
24
@binds << obj
-
24
@parts << Substitute.new
-
24
self
-
end
-
-
3
def add_binds(binds)
-
6
@binds.concat binds
-
6
binds.size.times do |i|
-
24
@parts << ", " unless i == 0
-
24
@parts << Substitute.new
-
end
-
6
self
-
end
-
-
3
def value
-
15
[@parts, @binds]
-
end
-
end
-
-
3
def self.query(sql)
-
1262
Query.new(sql)
-
end
-
-
3
def self.partial_query(values)
-
15
PartialQuery.new(values)
-
end
-
-
3
def self.partial_query_collector
-
15
PartialQueryCollector.new
-
end
-
-
3
class Params # :nodoc:
-
1364
def bind; Substitute.new; end
-
end
-
-
3
class BindMap # :nodoc:
-
3
def initialize(bound_attributes)
-
1277
@indexes = []
-
1277
@bound_attributes = bound_attributes
-
-
1277
bound_attributes.each_with_index do |attr, i|
-
2594
if ActiveModel::Attribute === attr && Substitute === attr.value
-
1361
@indexes << i
-
end
-
end
-
end
-
-
3
def bind(values)
-
6186
bas = @bound_attributes.dup
-
12682
@indexes.each_with_index { |offset, i| bas[offset] = bas[offset].with_cast_value(values[i]) }
-
6186
bas
-
end
-
end
-
-
3
def self.create(connection, callable = nil, &block)
-
1277
relation = (callable || block).call Params.new
-
1277
query_builder, binds = connection.cacheable_query(self, relation.arel)
-
1277
bind_map = BindMap.new(binds)
-
1277
new(query_builder, bind_map, relation.klass)
-
end
-
-
3
def initialize(query_builder, bind_map, klass)
-
1277
@query_builder = query_builder
-
1277
@bind_map = bind_map
-
1277
@klass = klass
-
end
-
-
3
def execute(params, connection, &block)
-
6186
bind_values = bind_map.bind params
-
-
6186
sql = query_builder.sql_for bind_values, connection
-
-
6186
klass.find_by_sql(sql, bind_values, preparable: true, &block)
-
rescue ::RangeError
-
[]
-
end
-
-
3
def self.unsupported_value?(value)
-
3128
case value
-
84
when NilClass, Array, Range, Hash, Relation, Base then true
-
end
-
end
-
-
3
private
-
3
attr_reader :query_builder, :bind_map, :klass
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/hash/indifferent_access"
-
-
3
module ActiveRecord
-
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
-
# It's like a simple key/value store baked into your record when you don't care about being able to
-
# query that store outside the context of a single record.
-
#
-
# You can then declare accessors to this store that are then accessible just like any other attribute
-
# of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
-
# already built around just accessing attributes on the model.
-
#
-
# Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+) and
-
# methods to access the changes made during the last save (+saved_change_to_key?+, +saved_change_to_key+ and
-
# +key_before_last_save+).
-
#
-
# NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead.
-
#
-
# Make sure that you declare the database column used for the serialized store as a text, so there's
-
# plenty of room.
-
#
-
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
-
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
-
#
-
# NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, or MySQL 5.7+
-
# +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
-
# Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
-
# the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
-
# using a symbol.
-
#
-
# NOTE: The default validations with the exception of +uniqueness+ will work.
-
# For example, if you want to check for +uniqueness+ with +hstore+ you will
-
# need to use a custom validation to handle it.
-
#
-
# Examples:
-
#
-
# class User < ActiveRecord::Base
-
# store :settings, accessors: [ :color, :homepage ], coder: JSON
-
# store :parent, accessors: [ :name ], coder: JSON, prefix: true
-
# store :spouse, accessors: [ :name ], coder: JSON, prefix: :partner
-
# store :settings, accessors: [ :two_factor_auth ], suffix: true
-
# store :settings, accessors: [ :login_retry ], suffix: :config
-
# end
-
#
-
# u = User.new(color: 'black', homepage: '37signals.com', parent_name: 'Mary', partner_name: 'Lily')
-
# u.color # Accessor stored attribute
-
# u.parent_name # Accessor stored attribute with prefix
-
# u.partner_name # Accessor stored attribute with custom prefix
-
# u.two_factor_auth_settings # Accessor stored attribute with suffix
-
# u.login_retry_config # Accessor stored attribute with custom suffix
-
# u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
-
#
-
# # There is no difference between strings and symbols for accessing custom attributes
-
# u.settings[:country] # => 'Denmark'
-
# u.settings['country'] # => 'Denmark'
-
#
-
# # Dirty tracking
-
# u.color = 'green'
-
# u.color_changed? # => true
-
# u.color_was # => 'black'
-
# u.color_change # => ['black', 'red']
-
#
-
# # Add additional accessors to an existing store through store_accessor
-
# class SuperUser < User
-
# store_accessor :settings, :privileges, :servants
-
# store_accessor :parent, :birthday, prefix: true
-
# store_accessor :settings, :secret_question, suffix: :config
-
# end
-
#
-
# The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes].
-
#
-
# User.stored_attributes[:settings] # [:color, :homepage, :two_factor_auth, :login_retry]
-
#
-
# == Overwriting default accessors
-
#
-
# All stored values are automatically available through accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling <tt>super</tt>
-
# to actually change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses a stored integer to hold the volume adjustment of the song
-
# store :settings, accessors: [:volume_adjustment]
-
#
-
# def volume_adjustment=(decibels)
-
# super(decibels.to_i)
-
# end
-
#
-
# def volume_adjustment
-
# super.to_i
-
# end
-
# end
-
3
module Store
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
3
class << self
-
3
attr_accessor :local_stored_attributes
-
end
-
end
-
-
3
module ClassMethods
-
3
def store(store_attribute, options = {})
-
27
serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
-
27
store_accessor(store_attribute, options[:accessors], **options.slice(:prefix, :suffix)) if options.has_key? :accessors
-
end
-
-
3
def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
-
61
keys = keys.flatten
-
-
61
accessor_prefix =
-
case prefix
-
when String, Symbol
-
6
"#{prefix}_"
-
when TrueClass
-
3
"#{store_attribute}_"
-
else
-
52
""
-
end
-
61
accessor_suffix =
-
case suffix
-
when String, Symbol
-
3
"_#{suffix}"
-
when TrueClass
-
3
"_#{store_attribute}"
-
else
-
55
""
-
end
-
-
61
_store_accessors_module.module_eval do
-
61
keys.each do |key|
-
80
accessor_key = "#{accessor_prefix}#{key}#{accessor_suffix}"
-
-
80
define_method("#{accessor_key}=") do |value|
-
1029
write_store_attribute(store_attribute, key, value)
-
end
-
-
80
define_method(accessor_key) do
-
144
read_store_attribute(store_attribute, key)
-
end
-
-
80
define_method("#{accessor_key}_changed?") do
-
30
return false unless attribute_changed?(store_attribute)
-
23
prev_store, new_store = changes[store_attribute]
-
23
prev_store&.dig(key) != new_store&.dig(key)
-
end
-
-
80
define_method("#{accessor_key}_change") do
-
26
return unless attribute_changed?(store_attribute)
-
20
prev_store, new_store = changes[store_attribute]
-
20
[prev_store&.dig(key), new_store&.dig(key)]
-
end
-
-
80
define_method("#{accessor_key}_was") do
-
20
return unless attribute_changed?(store_attribute)
-
17
prev_store, _new_store = changes[store_attribute]
-
17
prev_store&.dig(key)
-
end
-
-
80
define_method("saved_change_to_#{accessor_key}?") do
-
3
return false unless saved_change_to_attribute?(store_attribute)
-
3
prev_store, new_store = saved_change_to_attribute(store_attribute)
-
3
prev_store&.dig(key) != new_store&.dig(key)
-
end
-
-
80
define_method("saved_change_to_#{accessor_key}") do
-
3
return unless saved_change_to_attribute?(store_attribute)
-
3
prev_store, new_store = saved_change_to_attribute(store_attribute)
-
3
[prev_store&.dig(key), new_store&.dig(key)]
-
end
-
-
80
define_method("#{accessor_key}_before_last_save") do
-
3
return unless saved_change_to_attribute?(store_attribute)
-
3
prev_store, _new_store = saved_change_to_attribute(store_attribute)
-
3
prev_store&.dig(key)
-
end
-
end
-
end
-
-
# assign new store attribute and create new hash to ensure that each class in the hierarchy
-
# has its own hash of stored attributes.
-
61
self.local_stored_attributes ||= {}
-
61
self.local_stored_attributes[store_attribute] ||= []
-
61
self.local_stored_attributes[store_attribute] |= keys
-
end
-
-
3
def _store_accessors_module # :nodoc:
-
61
@_store_accessors_module ||= begin
-
28
mod = Module.new
-
28
include mod
-
28
mod
-
end
-
end
-
-
3
def stored_attributes
-
54
parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
-
54
if local_stored_attributes
-
36
parent.merge!(local_stored_attributes) { |k, a, b| a | b }
-
end
-
54
parent
-
end
-
end
-
-
3
private
-
3
def read_store_attribute(store_attribute, key) # :doc:
-
147
accessor = store_accessor_for(store_attribute)
-
147
accessor.read(self, store_attribute, key)
-
end
-
-
3
def write_store_attribute(store_attribute, key, value) # :doc:
-
1032
accessor = store_accessor_for(store_attribute)
-
1032
accessor.write(self, store_attribute, key, value)
-
end
-
-
3
def store_accessor_for(store_attribute)
-
1179
type_for_attribute(store_attribute).accessor
-
end
-
-
3
class HashAccessor # :nodoc:
-
3
def self.read(object, attribute, key)
-
1179
prepare(object, attribute)
-
1179
object.public_send(attribute)[key]
-
end
-
-
3
def self.write(object, attribute, key, value)
-
1032
prepare(object, attribute)
-
1032
if value != read(object, attribute, key)
-
1026
object.public_send :"#{attribute}_will_change!"
-
1026
object.public_send(attribute)[key] = value
-
end
-
end
-
-
3
def self.prepare(object, attribute)
-
135
object.public_send :"#{attribute}=", {} unless object.send(attribute)
-
end
-
end
-
-
3
class StringKeyedHashAccessor < HashAccessor # :nodoc:
-
3
def self.read(object, attribute, key)
-
99
super object, attribute, key.to_s
-
end
-
-
3
def self.write(object, attribute, key, value)
-
36
super object, attribute, key.to_s, value
-
end
-
end
-
-
3
class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
-
3
def self.prepare(object, store_attribute)
-
2076
attribute = object.send(store_attribute)
-
2076
unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
-
attribute = IndifferentCoder.as_indifferent_hash(attribute)
-
object.send :"#{store_attribute}=", attribute
-
end
-
2076
attribute
-
end
-
end
-
-
3
class IndifferentCoder # :nodoc:
-
3
def initialize(attr_name, coder_or_class_name)
-
27
@coder =
-
27
if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
-
9
coder_or_class_name
-
else
-
18
ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)
-
end
-
end
-
-
3
def dump(obj)
-
1740
@coder.dump self.class.as_indifferent_hash(obj)
-
end
-
-
3
def load(yaml)
-
5010
self.class.as_indifferent_hash(@coder.load(yaml || ""))
-
end
-
-
3
def self.as_indifferent_hash(obj)
-
6750
case obj
-
when ActiveSupport::HashWithIndifferentAccess
-
1914
obj
-
when Hash
-
1263
obj.with_indifferent_access
-
else
-
3573
ActiveSupport::HashWithIndifferentAccess.new
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# ActiveRecord::Suppressor prevents the receiver from being saved during
-
# a given block.
-
#
-
# For example, here's a pattern of creating notifications when new comments
-
# are posted. (The notification may in turn trigger an email, a push
-
# notification, or just appear in the UI somewhere):
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commentable, polymorphic: true
-
# after_create -> { Notification.create! comment: self,
-
# recipients: commentable.recipients }
-
# end
-
#
-
# That's what you want the bulk of the time. New comment creates a new
-
# Notification. But there may well be off cases, like copying a commentable
-
# and its comments, where you don't want that. So you'd have a concern
-
# something like this:
-
#
-
# module Copyable
-
# def copy_to(destination)
-
# Notification.suppress do
-
# # Copy logic that creates new comments that we do not want
-
# # triggering notifications.
-
# end
-
# end
-
# end
-
3
module Suppressor
-
3
extend ActiveSupport::Concern
-
-
3
module ClassMethods
-
3
def suppress(&block)
-
21
previous_state = SuppressorRegistry.suppressed[name]
-
21
SuppressorRegistry.suppressed[name] = true
-
21
yield
-
ensure
-
21
SuppressorRegistry.suppressed[name] = previous_state
-
end
-
end
-
-
3
def save(**) # :nodoc:
-
8192
SuppressorRegistry.suppressed[self.class.name] ? true : super
-
end
-
-
3
def save!(**) # :nodoc:
-
8271
SuppressorRegistry.suppressed[self.class.name] ? true : super
-
end
-
end
-
-
3
class SuppressorRegistry # :nodoc:
-
3
extend ActiveSupport::PerThreadRegistry
-
-
3
attr_reader :suppressed
-
-
3
def initialize
-
14
@suppressed = {}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
class TableMetadata # :nodoc:
-
3
delegate :join_primary_key, :join_foreign_key, :join_foreign_type, to: :reflection
-
-
3
def initialize(klass, arel_table, reflection = nil)
-
12018
@klass = klass
-
12018
@arel_table = arel_table
-
12018
@reflection = reflection
-
end
-
-
3
def type(column_name)
-
151665
arel_table.type_for_attribute(column_name)
-
end
-
-
3
def has_column?(column_name)
-
3976
klass&.columns_hash.key?(column_name)
-
end
-
-
3
def associated_with?(table_name)
-
49109
klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.singularize)
-
end
-
-
3
def associated_table(table_name)
-
4586
reflection = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
-
-
4586
if !reflection && table_name == arel_table.name
-
273
return self
-
end
-
-
4313
reflection ||= yield table_name if block_given?
-
-
4304
if reflection && !reflection.polymorphic?
-
3750
association_klass = reflection.klass
-
3750
arel_table = association_klass.arel_table
-
3750
arel_table = arel_table.alias(table_name) if arel_table.name != table_name
-
3750
TableMetadata.new(association_klass, arel_table, reflection)
-
else
-
554
type_caster = TypeCaster::Connection.new(klass, table_name)
-
554
arel_table = Arel::Table.new(table_name, type_caster: type_caster)
-
554
TableMetadata.new(nil, arel_table, reflection)
-
end
-
end
-
-
3
def polymorphic_association?
-
439
reflection&.polymorphic?
-
end
-
-
3
def through_association?
-
348
reflection&.through_reflection?
-
end
-
-
3
def reflect_on_aggregation(aggregation_name)
-
48745
klass&.reflect_on_aggregation(aggregation_name)
-
end
-
3
alias :aggregated_with? :reflect_on_aggregation
-
-
3
def predicate_builder
-
3949
if klass
-
3495
predicate_builder = klass.predicate_builder.dup
-
3495
predicate_builder.instance_variable_set(:@table, self)
-
3495
predicate_builder
-
else
-
454
PredicateBuilder.new(self)
-
end
-
end
-
-
3
attr_reader :arel_table
-
-
3
private
-
3
attr_reader :klass, :reflection
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/database_configurations"
-
-
3
module ActiveRecord
-
3
module Tasks # :nodoc:
-
3
class DatabaseNotSupported < StandardError; end # :nodoc:
-
-
# ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates
-
# logic behind common tasks used to manage database and migrations.
-
#
-
# The tasks defined here are used with Rails commands provided by Active Record.
-
#
-
# In order to use DatabaseTasks, a few config values need to be set. All the needed
-
# config values are set by Rails already, so it's necessary to do it only if you
-
# want to change the defaults or when you want to use Active Record outside of Rails
-
# (in such case after configuring the database tasks, you can also use the rake tasks
-
# defined in Active Record).
-
#
-
# The possible config values are:
-
#
-
# * +env+: current environment (like Rails.env).
-
# * +database_configuration+: configuration of your databases (as in +config/database.yml+).
-
# * +db_dir+: your +db+ directory.
-
# * +fixtures_path+: a path to fixtures directory.
-
# * +migrations_paths+: a list of paths to directories with migrations.
-
# * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
-
# * +root+: a path to the root of the application.
-
#
-
# Example usage of DatabaseTasks outside Rails could look as such:
-
#
-
# include ActiveRecord::Tasks
-
# DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
-
# DatabaseTasks.db_dir = 'db'
-
# # other settings...
-
#
-
# DatabaseTasks.create_current('production')
-
3
module DatabaseTasks
-
##
-
# :singleton-method:
-
# Extra flags passed to database CLI tool (mysqldump/pg_dump) when calling db:schema:dump
-
3
mattr_accessor :structure_dump_flags, instance_accessor: false
-
-
##
-
# :singleton-method:
-
# Extra flags passed to database CLI tool when calling db:schema:load
-
3
mattr_accessor :structure_load_flags, instance_accessor: false
-
-
3
extend self
-
-
3
attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
-
3
deprecate :current_config=
-
3
attr_accessor :database_configuration
-
-
3
LOCAL_HOSTS = ["127.0.0.1", "localhost"]
-
-
3
def check_protected_environments!
-
15
unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
-
15
current = ActiveRecord::Base.connection.migration_context.current_environment
-
15
stored = ActiveRecord::Base.connection.migration_context.last_stored_environment
-
-
12
if ActiveRecord::Base.connection.migration_context.protected_environment?
-
6
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
-
end
-
-
6
if stored && stored != current
-
raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
-
end
-
end
-
rescue ActiveRecord::NoDatabaseError
-
end
-
-
3
def register_task(pattern, task)
-
12
@tasks ||= {}
-
12
@tasks[pattern] = task
-
end
-
-
3
register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
-
3
register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks")
-
3
register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks")
-
-
3
def db_dir
-
@db_dir ||= Rails.application.config.paths["db"].first
-
end
-
-
3
def migrations_paths
-
@migrations_paths ||= Rails.application.paths["db/migrate"].to_a
-
end
-
-
3
def fixtures_path
-
@fixtures_path ||= if ENV["FIXTURES_PATH"]
-
File.join(root, ENV["FIXTURES_PATH"])
-
else
-
File.join(root, "test", "fixtures")
-
end
-
end
-
-
3
def root
-
@root ||= Rails.root
-
end
-
-
3
def env
-
@env ||= Rails.env
-
end
-
-
3
def spec
-
@spec ||= "primary"
-
end
-
3
deprecate spec: "please use name instead"
-
-
3
def name
-
@name ||= "primary"
-
end
-
-
3
def seed_loader
-
@seed_loader ||= Rails.application
-
end
-
-
3
def current_config(options = {})
-
18
if options.has_key?(:config)
-
6
@current_config = options[:config]
-
else
-
12
env_name = options[:env] || env
-
12
name = options[:spec] || "primary"
-
-
12
@current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: name)&.configuration_hash
-
end
-
end
-
3
deprecate :current_config
-
-
3
def create(configuration, *arguments)
-
29
db_config = resolve_configuration(configuration)
-
29
database_adapter_for(db_config, *arguments).create
-
21
$stdout.puts "Created database '#{db_config.database}'" if verbose?
-
rescue DatabaseAlreadyExists
-
5
$stderr.puts "Database '#{db_config.database}' already exists" if verbose?
-
rescue Exception => error
-
3
$stderr.puts error
-
3
$stderr.puts "Couldn't create '#{db_config.database}' database. Please check your configuration."
-
3
raise
-
end
-
-
3
def create_all
-
18
old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
-
27
each_local_configuration { |db_config| create(db_config) }
-
18
if old_pool
-
18
ActiveRecord::Base.connection_handler.establish_connection(old_pool.db_config)
-
end
-
end
-
-
3
def setup_initial_database_yaml
-
return {} unless defined?(Rails)
-
-
begin
-
Rails.application.config.load_database_yaml
-
rescue
-
$stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
-
-
{}
-
end
-
end
-
-
3
def for_each(databases)
-
return {} unless defined?(Rails)
-
-
database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
-
-
# if this is a single database application we don't want tasks for each primary database
-
return if database_configs.count == 1
-
-
database_configs.each do |db_config|
-
yield db_config.name
-
end
-
end
-
-
3
def raise_for_multi_db(environment = env, command:)
-
db_configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
-
-
if db_configs.count > 1
-
dbs_list = []
-
-
db_configs.each do |db|
-
dbs_list << "#{command}:#{db.name}"
-
end
-
-
raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}."
-
end
-
end
-
-
3
def create_current(environment = env, name = nil)
-
84
each_current_configuration(environment, name) { |db_config| create(db_config) }
-
30
ActiveRecord::Base.establish_connection(environment.to_sym)
-
end
-
-
3
def drop(configuration, *arguments)
-
22
db_config = resolve_configuration(configuration)
-
22
database_adapter_for(db_config, *arguments).drop
-
18
$stdout.puts "Dropped database '#{db_config.database}'" if verbose?
-
rescue ActiveRecord::NoDatabaseError
-
4
$stderr.puts "Database '#{db_config.database}' does not exist"
-
rescue Exception => error
-
$stderr.puts error
-
$stderr.puts "Couldn't drop database '#{db_config.database}'"
-
raise
-
end
-
-
3
def drop_all
-
27
each_local_configuration { |db_config| drop(db_config) }
-
end
-
-
3
def drop_current(environment = env)
-
78
each_current_configuration(environment) { |db_config| drop(db_config) }
-
end
-
-
3
def truncate_tables(db_config)
-
6
ActiveRecord::Base.establish_connection(db_config)
-
-
6
connection = ActiveRecord::Base.connection
-
6
connection.truncate_tables(*connection.tables)
-
end
-
3
private :truncate_tables
-
-
3
def truncate_all(environment = env)
-
18
ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config|
-
30
truncate_tables(db_config)
-
end
-
end
-
-
3
def migrate
-
33
check_target_version
-
-
9
scope = ENV["SCOPE"]
-
9
verbose_was, Migration.verbose = Migration.verbose, verbose?
-
-
9
Base.connection.migration_context.migrate(target_version) do |migration|
-
18
scope.blank? || scope == migration.scope
-
end
-
-
9
ActiveRecord::Base.clear_cache!
-
ensure
-
33
Migration.verbose = verbose_was
-
end
-
-
3
def migrate_status
-
1
unless ActiveRecord::Base.connection.schema_migration.table_exists?
-
Kernel.abort "Schema migrations table does not exist yet."
-
end
-
-
# output
-
1
puts "\ndatabase: #{ActiveRecord::Base.connection_db_config.database}\n\n"
-
1
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
-
1
puts "-" * 50
-
1
ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
-
3
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
-
end
-
1
puts
-
end
-
-
3
def check_target_version
-
69
if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"]))
-
42
raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`"
-
end
-
end
-
-
3
def target_version
-
93
ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty?
-
end
-
-
3
def charset_current(env_name = env, db_name = name)
-
3
db_config = ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: db_name)
-
3
charset(db_config)
-
end
-
-
3
def charset(configuration, *arguments)
-
12
db_config = resolve_configuration(configuration)
-
12
database_adapter_for(db_config, *arguments).charset
-
end
-
-
3
def collation_current(env_name = env, db_name = name)
-
3
db_config = ActiveRecord::Base.configurations.configs_for(env_name: env_name, name: db_name)
-
3
collation(db_config)
-
end
-
-
3
def collation(configuration, *arguments)
-
12
db_config = resolve_configuration(configuration)
-
12
database_adapter_for(db_config, *arguments).collation
-
end
-
-
3
def purge(configuration)
-
14
db_config = resolve_configuration(configuration)
-
14
database_adapter_for(db_config).purge
-
end
-
-
3
def purge_all
-
6
each_local_configuration { |db_config| purge(db_config) }
-
end
-
-
3
def purge_current(environment = env)
-
6
each_current_configuration(environment) { |db_config| purge(db_config) }
-
3
ActiveRecord::Base.establish_connection(environment.to_sym)
-
end
-
-
3
def structure_dump(configuration, *arguments)
-
29
db_config = resolve_configuration(configuration)
-
29
filename = arguments.delete_at(0)
-
29
database_adapter_for(db_config, *arguments).structure_dump(filename, structure_dump_flags)
-
end
-
-
3
def structure_load(configuration, *arguments)
-
14
db_config = resolve_configuration(configuration)
-
14
filename = arguments.delete_at(0)
-
14
database_adapter_for(db_config, *arguments).structure_load(filename, structure_load_flags)
-
end
-
-
3
def load_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
-
file ||= dump_filename(db_config.name, format)
-
-
verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"]
-
check_schema_file(file)
-
ActiveRecord::Base.establish_connection(db_config)
-
-
case format
-
when :ruby
-
load(file)
-
when :sql
-
structure_load(db_config, file)
-
else
-
raise ArgumentError, "unknown format #{format.inspect}"
-
end
-
ActiveRecord::InternalMetadata.create_table
-
ActiveRecord::InternalMetadata[:environment] = db_config.env_name
-
ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file)
-
ensure
-
Migration.verbose = verbose_was
-
end
-
-
3
def schema_up_to_date?(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = nil, name = nil)
-
db_config = resolve_configuration(configuration)
-
-
if environment || name
-
ActiveSupport::Deprecation.warn("`environment` and `name` will be removed as parameters in 6.2.0, you may now pass an ActiveRecord::DatabaseConfigurations::DatabaseConfig as `configuration` instead.")
-
end
-
-
name ||= db_config.name
-
-
file ||= dump_filename(name, format)
-
-
return true unless File.exist?(file)
-
-
ActiveRecord::Base.establish_connection(db_config)
-
-
return false unless ActiveRecord::InternalMetadata.enabled?
-
return false unless ActiveRecord::InternalMetadata.table_exists?
-
-
ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file)
-
end
-
-
3
def reconstruct_from_schema(db_config, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
-
file ||= dump_filename(db_config.name, format)
-
-
check_schema_file(file)
-
-
ActiveRecord::Base.establish_connection(db_config)
-
-
if schema_up_to_date?(db_config, format, file)
-
truncate_tables(db_config)
-
else
-
purge(db_config)
-
load_schema(db_config, format, file)
-
end
-
rescue ActiveRecord::NoDatabaseError
-
create(db_config)
-
load_schema(db_config, format, file)
-
end
-
-
3
def dump_schema(db_config, format = ActiveRecord::Base.schema_format) # :nodoc:
-
require "active_record/schema_dumper"
-
filename = dump_filename(db_config.name, format)
-
connection = ActiveRecord::Base.connection
-
-
case format
-
when :ruby
-
File.open(filename, "w:utf-8") do |file|
-
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
-
end
-
when :sql
-
structure_dump(db_config, filename)
-
if connection.schema_migration.table_exists?
-
File.open(filename, "a") do |f|
-
f.puts connection.dump_schema_information
-
f.print "\n"
-
end
-
end
-
end
-
end
-
-
3
def schema_file(format = ActiveRecord::Base.schema_format)
-
9
File.join(db_dir, schema_file_type(format))
-
end
-
-
3
def schema_file_type(format = ActiveRecord::Base.schema_format)
-
9
case format
-
when :ruby
-
6
"schema.rb"
-
when :sql
-
3
"structure.sql"
-
end
-
end
-
-
3
def dump_filename(name, format = ActiveRecord::Base.schema_format)
-
filename = if name == "primary"
-
schema_file_type(format)
-
else
-
"#{name}_#{schema_file_type(format)}"
-
end
-
-
ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
-
end
-
-
3
def cache_dump_filename(name, schema_cache_path: nil)
-
12
filename = if name == "primary"
-
9
"schema_cache.yml"
-
else
-
3
"#{name}_schema_cache.yml"
-
end
-
-
12
schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
-
end
-
-
3
def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
-
each_current_configuration(environment) do |db_config|
-
load_schema(db_config, format, file)
-
end
-
ActiveRecord::Base.establish_connection(environment.to_sym)
-
end
-
-
3
def check_schema_file(filename)
-
3
unless File.exist?(filename)
-
3
message = +%{#{filename} doesn't exist yet. Run `bin/rails db:migrate` to create it, then try again.}
-
3
message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root)
-
3
Kernel.abort message
-
end
-
end
-
-
3
def load_seed
-
if seed_loader
-
seed_loader.load_seed
-
else
-
raise "You tried to load seed data, but no seed loader is specified. Please specify seed " \
-
"loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" \
-
"Seed loader should respond to load_seed method"
-
end
-
end
-
-
# Dumps the schema cache in YAML format for the connection into the file
-
#
-
# ==== Examples:
-
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml")
-
3
def dump_schema_cache(conn, filename)
-
3
conn.schema_cache.dump_to(filename)
-
end
-
-
3
def clear_schema_cache(filename)
-
3
FileUtils.rm_f filename, verbose: false
-
end
-
-
3
private
-
3
def resolve_configuration(configuration)
-
132
Base.configurations.resolve(configuration)
-
end
-
-
3
def verbose?
-
53
ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
-
end
-
-
# Create a new instance for the specified db configuration object
-
# For classes that have been converted to use db_config objects, pass a
-
# `DatabaseConfig`, otherwise pass a `Hash`
-
3
def database_adapter_for(db_config, *arguments)
-
132
klass = class_for_adapter(db_config.adapter)
-
129
converted = klass.respond_to?(:using_database_configurations?) && klass.using_database_configurations?
-
-
129
config = converted ? db_config : db_config.configuration_hash
-
129
klass.new(config, *arguments)
-
end
-
-
3
def class_for_adapter(adapter)
-
440
_key, task = @tasks.each_pair.detect { |pattern, _task| adapter[pattern] }
-
132
unless task
-
3
raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter"
-
end
-
129
task.is_a?(String) ? task.constantize : task
-
end
-
-
3
def each_current_configuration(environment, name = nil)
-
57
environments = [environment]
-
57
environments << "test" if environment == "development" && !ENV["DATABASE_URL"]
-
-
57
environments.each do |env|
-
87
ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
-
111
next if name && name != db_config.name
-
-
111
yield db_config
-
end
-
end
-
end
-
-
3
def each_local_configuration
-
39
ActiveRecord::Base.configurations.configs_for.each do |db_config|
-
39
next unless db_config.database
-
-
33
if local_database?(db_config)
-
21
yield db_config
-
else
-
12
$stderr.puts "This task only modifies local databases. #{db_config.database} is on a remote host."
-
end
-
end
-
end
-
-
3
def local_database?(db_config)
-
33
host = db_config.host
-
33
host.blank? || LOCAL_HOSTS.include?(host)
-
end
-
-
3
def schema_sha1(file)
-
Digest::SHA1.hexdigest(File.read(file))
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Tasks # :nodoc:
-
3
class MySQLDatabaseTasks # :nodoc:
-
3
ER_DB_CREATE_EXISTS = 1007
-
-
3
delegate :connection, :establish_connection, to: ActiveRecord::Base
-
-
3
def self.using_database_configurations?
-
21
true
-
end
-
-
3
def initialize(db_config)
-
@db_config = db_config
-
@configuration_hash = db_config.configuration_hash
-
end
-
-
3
def create
-
establish_connection(configuration_hash_without_database)
-
connection.create_database(db_config.database, creation_options)
-
establish_connection(db_config)
-
end
-
-
3
def drop
-
establish_connection(db_config)
-
connection.drop_database(db_config.database)
-
end
-
-
3
def purge
-
establish_connection(db_config)
-
connection.recreate_database(db_config.database, creation_options)
-
end
-
-
3
def charset
-
connection.charset
-
end
-
-
3
def collation
-
connection.collation
-
end
-
-
3
def structure_dump(filename, extra_flags)
-
args = prepare_command_options
-
args.concat(["--result-file", "#{filename}"])
-
args.concat(["--no-data"])
-
args.concat(["--routines"])
-
args.concat(["--skip-comments"])
-
-
ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
-
if ignore_tables.any?
-
args += ignore_tables.map { |table| "--ignore-table=#{db_config.database}.#{table}" }
-
end
-
-
args.concat([db_config.database.to_s])
-
args.unshift(*extra_flags) if extra_flags
-
-
run_cmd("mysqldump", args, "dumping")
-
end
-
-
3
def structure_load(filename, extra_flags)
-
args = prepare_command_options
-
args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
-
args.concat(["--database", db_config.database.to_s])
-
args.unshift(*extra_flags) if extra_flags
-
-
run_cmd("mysql", args, "loading")
-
end
-
-
3
private
-
3
attr_reader :db_config, :configuration_hash
-
-
3
def configuration_hash_without_database
-
configuration_hash.merge(database: nil)
-
end
-
-
3
def creation_options
-
Hash.new.tap do |options|
-
options[:charset] = configuration_hash[:encoding] if configuration_hash.include?(:encoding)
-
options[:collation] = configuration_hash[:collation] if configuration_hash.include?(:collation)
-
end
-
end
-
-
3
def prepare_command_options
-
args = {
-
host: "--host",
-
port: "--port",
-
socket: "--socket",
-
username: "--user",
-
password: "--password",
-
encoding: "--default-character-set",
-
sslca: "--ssl-ca",
-
sslcert: "--ssl-cert",
-
sslcapath: "--ssl-capath",
-
sslcipher: "--ssl-cipher",
-
sslkey: "--ssl-key"
-
}.map { |opt, arg| "#{arg}=#{configuration_hash[opt]}" if configuration_hash[opt] }.compact
-
-
args
-
end
-
-
3
def run_cmd(cmd, args, action)
-
fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
-
end
-
-
3
def run_cmd_error(cmd, args, action)
-
msg = +"failed to execute: `#{cmd}`\n"
-
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
-
msg
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "tempfile"
-
-
3
module ActiveRecord
-
3
module Tasks # :nodoc:
-
3
class PostgreSQLDatabaseTasks # :nodoc:
-
3
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
-
3
ON_ERROR_STOP_1 = "ON_ERROR_STOP=1"
-
3
SQL_COMMENT_BEGIN = "--"
-
-
3
delegate :connection, :establish_connection, :clear_active_connections!,
-
to: ActiveRecord::Base
-
-
3
def self.using_database_configurations?
-
50
true
-
end
-
-
3
def initialize(db_config)
-
29
@db_config = db_config
-
29
@configuration_hash = db_config.configuration_hash
-
end
-
-
3
def create(master_established = false)
-
13
establish_master_connection unless master_established
-
12
connection.create_database(db_config.database, configuration_hash.merge(encoding: encoding))
-
11
establish_connection(db_config)
-
end
-
-
3
def drop
-
8
establish_master_connection
-
8
connection.drop_database(db_config.database)
-
end
-
-
3
def charset
-
1
connection.encoding
-
end
-
-
3
def collation
-
1
connection.collation
-
end
-
-
3
def purge
-
5
clear_active_connections!
-
5
drop
-
5
create true
-
end
-
-
3
def structure_dump(filename, extra_flags)
-
8
set_psql_env
-
-
8
search_path = \
-
case ActiveRecord::Base.dump_schemas
-
when :schema_search_path
-
6
configuration_hash[:schema_search_path]
-
when :all
-
1
nil
-
when String
-
1
ActiveRecord::Base.dump_schemas
-
end
-
-
8
args = ["--schema-only", "--no-privileges", "--no-owner", "--file", filename]
-
8
args.concat(Array(extra_flags)) if extra_flags
-
8
unless search_path.blank?
-
2
args += search_path.split(",").map do |part|
-
4
"--schema=#{part.strip}"
-
end
-
end
-
-
8
ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
-
8
if ignore_tables.any?
-
3
args += ignore_tables.flat_map { |table| ["-T", table] }
-
end
-
-
8
args << db_config.database
-
8
run_cmd("pg_dump", args, "dumping")
-
7
remove_sql_header_comments(filename)
-
14
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
-
end
-
-
3
def structure_load(filename, extra_flags)
-
3
set_psql_env
-
3
args = ["--set", ON_ERROR_STOP_1, "--quiet", "--no-psqlrc", "--file", filename]
-
3
args.concat(Array(extra_flags)) if extra_flags
-
3
args << db_config.database
-
3
run_cmd("psql", args, "loading")
-
end
-
-
3
private
-
3
attr_reader :db_config, :configuration_hash
-
-
3
def encoding
-
12
configuration_hash[:encoding] || DEFAULT_ENCODING
-
end
-
-
3
def establish_master_connection
-
16
establish_connection configuration_hash.merge(
-
database: "postgres",
-
schema_search_path: "public"
-
)
-
end
-
-
3
def set_psql_env
-
11
ENV["PGHOST"] = db_config.host if db_config.host
-
11
ENV["PGPORT"] = configuration_hash[:port].to_s if configuration_hash[:port]
-
11
ENV["PGPASSWORD"] = configuration_hash[:password].to_s if configuration_hash[:password]
-
11
ENV["PGUSER"] = configuration_hash[:username].to_s if configuration_hash[:username]
-
end
-
-
3
def run_cmd(cmd, args, action)
-
11
fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
-
end
-
-
3
def run_cmd_error(cmd, args, action)
-
1
msg = +"failed to execute:\n"
-
1
msg << "#{cmd} #{args.join(' ')}\n\n"
-
1
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
-
1
msg
-
end
-
-
3
def remove_sql_header_comments(filename)
-
7
removing_comments = true
-
7
tempfile = Tempfile.open("uncommented_structure.sql")
-
7
begin
-
7
File.foreach(filename) do |line|
-
5
unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
-
2
tempfile << line
-
2
removing_comments = false
-
end
-
end
-
ensure
-
7
tempfile.close
-
end
-
7
FileUtils.cp(tempfile.path, filename)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Tasks # :nodoc:
-
3
class SQLiteDatabaseTasks # :nodoc:
-
3
delegate :connection, :establish_connection, to: ActiveRecord::Base
-
-
3
def self.using_database_configurations?
-
55
true
-
end
-
-
3
def initialize(db_config, root = ActiveRecord::Tasks::DatabaseTasks.root)
-
34
@db_config = db_config
-
34
@root = root
-
end
-
-
3
def create
-
12
raise DatabaseAlreadyExists if File.exist?(db_config.database)
-
-
8
establish_connection(db_config)
-
6
connection
-
end
-
-
3
def drop
-
10
require "pathname"
-
10
path = Pathname.new(db_config.database)
-
10
file = path.absolute? ? path.to_s : File.join(root, path)
-
-
10
FileUtils.rm(file)
-
rescue Errno::ENOENT => error
-
4
raise NoDatabaseError.new(error.message)
-
end
-
-
3
def purge
-
drop
-
rescue NoDatabaseError
-
ensure
-
create
-
end
-
-
3
def charset
-
2
connection.encoding
-
end
-
-
3
def structure_dump(filename, extra_flags)
-
6
args = []
-
6
args.concat(Array(extra_flags)) if extra_flags
-
6
args << db_config.database
-
-
6
ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
-
6
if ignore_tables.any?
-
4
condition = ignore_tables.map { |table| connection.quote(table) }.join(", ")
-
2
args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
-
else
-
4
args << ".schema"
-
end
-
6
run_cmd("sqlite3", args, filename)
-
end
-
-
3
def structure_load(filename, extra_flags)
-
2
flags = extra_flags.join(" ") if extra_flags
-
2
`sqlite3 #{flags} #{db_config.database} < "#{filename}"`
-
end
-
-
3
private
-
3
attr_reader :db_config, :root
-
-
3
def run_cmd(cmd, args, out)
-
6
fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
-
end
-
-
3
def run_cmd_error(cmd, args)
-
2
msg = +"failed to execute:\n"
-
2
msg << "#{cmd} #{args.join(' ')}\n\n"
-
2
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
-
2
msg
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/testing/parallelization"
-
-
3
module ActiveRecord
-
3
module TestDatabases # :nodoc:
-
3
ActiveSupport::Testing::Parallelization.after_fork_hook do |i|
-
4
create_and_load_schema(i, env_name: ActiveRecord::ConnectionHandling::DEFAULT_ENV.call)
-
end
-
-
3
def self.create_and_load_schema(i, env_name:)
-
6
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
-
-
6
ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
-
8
db_config._database = "#{db_config.database}-#{i}"
-
-
8
ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, ActiveRecord::Base.schema_format, nil)
-
end
-
ensure
-
6
ActiveRecord::Base.establish_connection
-
6
ENV["VERBOSE"] = old
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
-
3
module ActiveRecord
-
3
module TestFixtures
-
3
extend ActiveSupport::Concern
-
-
3
def before_setup # :nodoc:
-
19030
setup_fixtures
-
19030
super
-
end
-
-
3
def after_teardown # :nodoc:
-
19030
super
-
19030
teardown_fixtures
-
end
-
-
3
included do
-
13
class_attribute :fixture_path, instance_writer: false
-
13
class_attribute :fixture_table_names, default: []
-
13
class_attribute :fixture_class_names, default: {}
-
13
class_attribute :use_transactional_tests, default: true
-
13
class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
-
13
class_attribute :pre_loaded_fixtures, default: false
-
13
class_attribute :lock_threads, default: true
-
end
-
-
3
module ClassMethods
-
# Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
-
#
-
# Examples:
-
#
-
# set_fixture_class some_fixture: SomeModel,
-
# 'namespaced/fixture' => Another::Model
-
#
-
# The keys must be the fixture names, that coincide with the short paths to the fixture files.
-
3
def set_fixture_class(class_names = {})
-
24
self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
-
end
-
-
3
def fixtures(*fixture_set_names)
-
479
if fixture_set_names.first == :all
-
14
raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
-
11
fixture_set_names = Dir[::File.join(fixture_path, "{**,*}/*.{yml}")].uniq
-
29
fixture_set_names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
-
61
fixture_set_names.map! { |f| f[fixture_path.to_s.size..-5].delete_prefix("/") }
-
else
-
465
fixture_set_names = fixture_set_names.flatten.map(&:to_s)
-
end
-
-
476
self.fixture_table_names |= fixture_set_names
-
476
setup_fixture_accessors(fixture_set_names)
-
end
-
-
3
def setup_fixture_accessors(fixture_set_names = nil)
-
479
fixture_set_names = Array(fixture_set_names || fixture_table_names)
-
479
methods = Module.new do
-
479
fixture_set_names.each do |fs_name|
-
2043
fs_name = fs_name.to_s
-
2043
accessor_name = fs_name.tr("/", "_").to_sym
-
-
2043
define_method(accessor_name) do |*fixture_names|
-
7382
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
-
7382
return_single_record = fixture_names.size == 1
-
7382
fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
-
-
7382
@fixture_cache[fs_name] ||= {}
-
-
7382
instances = fixture_names.map do |f_name|
-
7553
f_name = f_name.to_s if f_name.is_a?(Symbol)
-
7553
@fixture_cache[fs_name].delete(f_name) if force_reload
-
-
7553
if @loaded_fixtures[fs_name][f_name]
-
7526
@fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
-
else
-
27
raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
-
end
-
end
-
-
7346
return_single_record ? instances.first : instances
-
end
-
2043
private accessor_name
-
end
-
end
-
479
include methods
-
end
-
-
3
def uses_transaction(*methods)
-
5
@uses_transaction = [] unless defined?(@uses_transaction)
-
5
@uses_transaction.concat methods.map(&:to_s)
-
end
-
-
3
def uses_transaction?(method)
-
33195
@uses_transaction = [] unless defined?(@uses_transaction)
-
33195
@uses_transaction.include?(method.to_s)
-
end
-
end
-
-
3
def run_in_transaction?
-
38063
use_transactional_tests &&
-
!self.class.uses_transaction?(name)
-
end
-
-
3
def setup_fixtures(config = ActiveRecord::Base)
-
19030
if pre_loaded_fixtures && !use_transactional_tests
-
raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
-
end
-
-
19030
@fixture_cache = {}
-
19030
@fixture_connections = []
-
19030
@@already_loaded_fixtures ||= {}
-
19030
@connection_subscriber = nil
-
-
# Load fixtures once and begin transaction.
-
19030
if run_in_transaction?
-
16591
if @@already_loaded_fixtures[self.class]
-
15644
@loaded_fixtures = @@already_loaded_fixtures[self.class]
-
else
-
947
@loaded_fixtures = load_fixtures(config)
-
944
@@already_loaded_fixtures[self.class] = @loaded_fixtures
-
end
-
-
# Begin transactions for connections already established
-
16588
@fixture_connections = enlist_fixture_connections
-
16588
@fixture_connections.each do |connection|
-
109250
connection.begin_transaction joinable: false, _lazy: false
-
109250
connection.pool.lock_thread = true if lock_threads
-
end
-
-
# When connections are established in the future, begin a transaction too
-
16588
@connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
-
78
spec_name = payload[:spec_name] if payload.key?(:spec_name)
-
78
setup_shared_connection_pool
-
-
78
if spec_name
-
78
begin
-
78
connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
-
rescue ConnectionNotEstablished
-
connection = nil
-
end
-
-
78
if connection && !@fixture_connections.include?(connection)
-
70
connection.begin_transaction joinable: false, _lazy: false
-
70
connection.pool.lock_thread = true if lock_threads
-
70
@fixture_connections << connection
-
end
-
end
-
end
-
-
# Load fixtures for every test.
-
else
-
2439
ActiveRecord::FixtureSet.reset_cache
-
2439
@@already_loaded_fixtures[self.class] = nil
-
2439
@loaded_fixtures = load_fixtures(config)
-
end
-
-
# Instantiate fixtures for every test if requested.
-
19027
instantiate_fixtures if use_instantiated_fixtures
-
end
-
-
3
def teardown_fixtures
-
# Rollback changes if a transaction is active.
-
19033
if run_in_transaction?
-
16594
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
-
16594
@fixture_connections.each do |connection|
-
109320
connection.rollback_transaction if connection.transaction_open?
-
109320
connection.pool.lock_thread = false
-
end
-
16594
@fixture_connections.clear
-
else
-
2439
ActiveRecord::FixtureSet.reset_cache
-
end
-
-
19033
ActiveRecord::Base.clear_active_connections!
-
end
-
-
3
def enlist_fixture_connections
-
16588
setup_shared_connection_pool
-
-
16588
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
-
end
-
-
3
private
-
# Shares the writing connection pool with connections on
-
# other handlers.
-
#
-
# In an application with a primary and replica the test fixtures
-
# need to share a connection pool so that the reading connection
-
# can see data in the open transaction on the writing connection.
-
3
def setup_shared_connection_pool
-
16666
writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role]
-
-
16666
ActiveRecord::Base.connection_handlers.values.each do |handler|
-
16668
if handler != writing_handler
-
6
handler.connection_pool_names.each do |name|
-
6
writing_pool_manager = writing_handler.send(:owner_to_pool_manager)[name]
-
6
return unless writing_pool_manager
-
-
6
writing_pool_config = writing_pool_manager.get_pool_config(:default)
-
-
6
pool_manager = handler.send(:owner_to_pool_manager)[name]
-
6
pool_manager.set_pool_config(:default, writing_pool_config)
-
end
-
end
-
end
-
end
-
-
3
def load_fixtures(config)
-
3383
ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config).index_by(&:name)
-
end
-
-
3
def instantiate_fixtures
-
142
if pre_loaded_fixtures
-
raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
-
ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
-
else
-
142
raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil?
-
142
@loaded_fixtures.each_value do |fixture_set|
-
791
ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
-
end
-
end
-
end
-
-
3
def load_instances?
-
791
use_instantiated_fixtures != :no_instances
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record \Timestamp
-
#
-
# Active Record automatically timestamps create and update operations if the
-
# table has fields named <tt>created_at/created_on</tt> or
-
# <tt>updated_at/updated_on</tt>.
-
#
-
# Timestamping can be turned off by setting:
-
#
-
# config.active_record.record_timestamps = false
-
#
-
# Timestamps are in UTC by default but you can use the local timezone by setting:
-
#
-
# config.active_record.default_timezone = :local
-
#
-
# == Time Zone aware attributes
-
#
-
# Active Record keeps all the <tt>datetime</tt> and <tt>time</tt> columns
-
# timezone aware. By default, these values are stored in the database as UTC
-
# and converted back to the current <tt>Time.zone</tt> when pulled from the database.
-
#
-
# This feature can be turned off completely by setting:
-
#
-
# config.active_record.time_zone_aware_attributes = false
-
#
-
# You can also specify that only <tt>datetime</tt> columns should be time-zone
-
# aware (while <tt>time</tt> should not) by setting:
-
#
-
# ActiveRecord::Base.time_zone_aware_types = [:datetime]
-
#
-
# You can also add database specific timezone aware types. For example, for PostgreSQL:
-
#
-
# ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange]
-
#
-
# Finally, you can indicate specific attributes of a model for which time zone
-
# conversion should not applied, for instance by setting:
-
#
-
# class Topic < ActiveRecord::Base
-
# self.skip_time_zone_conversion_for_attributes = [:written_on]
-
# end
-
3
module Timestamp
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
3
class_attribute :record_timestamps, default: true
-
end
-
-
3
def initialize_dup(other) # :nodoc:
-
111
super
-
111
clear_timestamp_attributes
-
end
-
-
3
module ClassMethods # :nodoc:
-
3
def touch_attributes_with_time(*names, time: nil)
-
156
attribute_names = timestamp_attributes_for_update_in_model
-
156
attribute_names |= names.map(&:to_s)
-
156
attribute_names.index_with(time || current_time_from_proper_timezone)
-
end
-
-
3
def timestamp_attributes_for_create_in_model
-
1925
@timestamp_attributes_for_create_in_model ||=
-
1922
(timestamp_attributes_for_create & column_names).freeze
-
end
-
-
3
def timestamp_attributes_for_update_in_model
-
4823
@timestamp_attributes_for_update_in_model ||=
-
1991
(timestamp_attributes_for_update & column_names).freeze
-
end
-
-
3
def all_timestamp_attributes_in_model
-
303700
@all_timestamp_attributes_in_model ||=
-
1922
(timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model).freeze
-
end
-
-
3
def current_time_from_proper_timezone
-
14424
default_timezone == :utc ? Time.now.utc : Time.now
-
end
-
-
3
private
-
3
def timestamp_attributes_for_create
-
5766
["created_at", "created_on"].map! { |name| attribute_aliases[name] || name }
-
end
-
-
3
def timestamp_attributes_for_update
-
5973
["updated_at", "updated_on"].map! { |name| attribute_aliases[name] || name }
-
end
-
-
3
def reload_schema_from_cache
-
4565
@timestamp_attributes_for_create_in_model = nil
-
4565
@timestamp_attributes_for_update_in_model = nil
-
4565
@all_timestamp_attributes_in_model = nil
-
4565
super
-
end
-
end
-
-
3
private
-
3
def _create_record
-
12451
if record_timestamps
-
12013
current_time = current_time_from_proper_timezone
-
-
12013
all_timestamp_attributes_in_model.each do |column|
-
9745
_write_attribute(column, current_time) unless _read_attribute(column)
-
end
-
end
-
-
12447
super
-
end
-
-
3
def _update_record
-
3419
if @_touch_record && should_record_timestamps?
-
1724
current_time = current_time_from_proper_timezone
-
-
1724
timestamp_attributes_for_update_in_model.each do |column|
-
1160
next if will_save_change_to_attribute?(column)
-
1143
_write_attribute(column, current_time)
-
end
-
end
-
-
3419
super
-
end
-
-
3
def create_or_update(touch: true, **)
-
16016
@_touch_record = touch
-
16016
super
-
end
-
-
3
def should_record_timestamps?
-
3416
record_timestamps && (!partial_writes? || has_changes_to_save?)
-
end
-
-
3
def timestamp_attributes_for_create_in_model
-
3
self.class.timestamp_attributes_for_create_in_model
-
end
-
-
3
def timestamp_attributes_for_update_in_model
-
2684
self.class.timestamp_attributes_for_update_in_model
-
end
-
-
3
def all_timestamp_attributes_in_model
-
12130
self.class.all_timestamp_attributes_in_model
-
end
-
-
3
def current_time_from_proper_timezone
-
14286
self.class.current_time_from_proper_timezone
-
end
-
-
3
def max_updated_column_timestamp
-
timestamp_attributes_for_update_in_model
-
87
.map { |attr| self[attr]&.to_time }
-
.compact
-
51
.max
-
end
-
-
# Clear attributes and changed_attributes
-
3
def clear_timestamp_attributes
-
114
all_timestamp_attributes_in_model.each do |attribute_name|
-
174
self[attribute_name] = nil
-
174
clear_attribute_change(attribute_name)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record Touch Later
-
3
module TouchLater # :nodoc:
-
3
def before_committed!
-
16238
touch_deferred_attributes if has_defer_touch_attrs? && persisted?
-
16238
super
-
end
-
-
3
def touch_later(*names) # :nodoc:
-
423
_raise_record_not_touched_error unless persisted?
-
-
420
@_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
-
@_defer_touch_attrs |= names.map! do |name|
-
15
name = name.to_s
-
15
self.class.attribute_aliases[name] || name
-
420
end unless names.empty?
-
-
420
@_touch_time = current_time_from_proper_timezone
-
-
420
surreptitiously_touch @_defer_touch_attrs
-
420
add_to_transaction
-
420
@_new_record_before_last_commit ||= false
-
-
# touch the parents as we are not calling the after_save callbacks
-
420
self.class.reflect_on_all_associations(:belongs_to).each do |r|
-
105
if touch = r.options[:touch]
-
48
ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later)
-
end
-
end
-
end
-
-
3
def touch(*names, time: nil) # :nodoc:
-
537
if has_defer_touch_attrs?
-
348
names |= @_defer_touch_attrs
-
348
super(*names, time: time)
-
348
@_defer_touch_attrs, @_touch_time = nil, nil
-
else
-
189
super
-
end
-
end
-
-
3
private
-
3
def surreptitiously_touch(attr_names)
-
420
attr_names.each do |attr_name|
-
435
_write_attribute(attr_name, @_touch_time)
-
435
clear_attribute_change(attr_name)
-
end
-
end
-
-
3
def touch_deferred_attributes
-
342
@_skip_dirty_tracking = true
-
342
touch(time: @_touch_time)
-
end
-
-
3
def has_defer_touch_attrs?
-
16775
defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
-
end
-
-
3
def belongs_to_touch_method
-
588
:touch_later
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# See ActiveRecord::Transactions::ClassMethods for documentation.
-
3
module Transactions
-
3
extend ActiveSupport::Concern
-
#:nodoc:
-
3
ACTIONS = [:create, :destroy, :update]
-
-
3
included do
-
3
define_callbacks :commit, :rollback,
-
:before_commit,
-
scope: [:kind, :name]
-
end
-
-
# = Active Record Transactions
-
#
-
# \Transactions are protective blocks where SQL statements are only permanent
-
# if they can all succeed as one atomic action. The classic example is a
-
# transfer between two accounts where you can only have a deposit if the
-
# withdrawal succeeded and vice versa. \Transactions enforce the integrity of
-
# the database and guard the data against program errors or database
-
# break-downs. So basically you should use transaction blocks whenever you
-
# have a number of statements that must be executed together or not at all.
-
#
-
# For example:
-
#
-
# ActiveRecord::Base.transaction do
-
# david.withdrawal(100)
-
# mary.deposit(100)
-
# end
-
#
-
# This example will only take money from David and give it to Mary if neither
-
# +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
-
# ROLLBACK that returns the database to the state before the transaction
-
# began. Be aware, though, that the objects will _not_ have their instance
-
# data returned to their pre-transactional state.
-
#
-
# == Different Active Record classes in a single transaction
-
#
-
# Though the #transaction class method is called on some Active Record class,
-
# the objects within the transaction block need not all be instances of
-
# that class. This is because transactions are per-database connection, not
-
# per-model.
-
#
-
# In this example a +balance+ record is transactionally saved even
-
# though #transaction is called on the +Account+ class:
-
#
-
# Account.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# The #transaction method is also available as a model instance method.
-
# For example, you can also do this:
-
#
-
# balance.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# == Transactions are not distributed across database connections
-
#
-
# A transaction acts on a single database connection. If you have
-
# multiple class-specific databases, the transaction will not protect
-
# interaction among them. One workaround is to begin a transaction
-
# on each class whose models you alter:
-
#
-
# Student.transaction do
-
# Course.transaction do
-
# course.enroll(student)
-
# student.units += course.units
-
# end
-
# end
-
#
-
# This is a poor solution, but fully distributed transactions are beyond
-
# the scope of Active Record.
-
#
-
# == +save+ and +destroy+ are automatically wrapped in a transaction
-
#
-
# Both {#save}[rdoc-ref:Persistence#save] and
-
# {#destroy}[rdoc-ref:Persistence#destroy] come wrapped in a transaction that ensures
-
# that whatever you do in validations or callbacks will happen under its
-
# protected cover. So you can use validations to check for values that
-
# the transaction depends on or you can raise exceptions in the callbacks
-
# to rollback, including <tt>after_*</tt> callbacks.
-
#
-
# As a consequence changes to the database are not seen outside your connection
-
# until the operation is complete. For example, if you try to update the index
-
# of a search engine in +after_save+ the indexer won't see the updated record.
-
# The #after_commit callback is the only one that is triggered once the update
-
# is committed. See below.
-
#
-
# == Exception handling and rolling back
-
#
-
# Also have in mind that exceptions thrown within a transaction block will
-
# be propagated (after triggering the ROLLBACK), so you should be ready to
-
# catch those in your application code.
-
#
-
# One exception is the ActiveRecord::Rollback exception, which will trigger
-
# a ROLLBACK when raised, but not be re-raised by the transaction block.
-
#
-
# *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
-
# inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an
-
# error occurred at the database level, for example when a unique constraint
-
# is violated. On some database systems, such as PostgreSQL, database errors
-
# inside a transaction cause the entire transaction to become unusable
-
# until it's restarted from the beginning. Here is an example which
-
# demonstrates the problem:
-
#
-
# # Suppose that we have a Number model with a unique column called 'i'.
-
# Number.transaction do
-
# Number.create(i: 0)
-
# begin
-
# # This will raise a unique constraint error...
-
# Number.create(i: 0)
-
# rescue ActiveRecord::StatementInvalid
-
# # ...which we ignore.
-
# end
-
#
-
# # On PostgreSQL, the transaction is now unusable. The following
-
# # statement will cause a PostgreSQL error, even though the unique
-
# # constraint is no longer violated:
-
# Number.create(i: 1)
-
# # => "PG::Error: ERROR: current transaction is aborted, commands
-
# # ignored until end of transaction block"
-
# end
-
#
-
# One should restart the entire transaction if an
-
# ActiveRecord::StatementInvalid occurred.
-
#
-
# == Nested transactions
-
#
-
# #transaction calls can be nested. By default, this makes all database
-
# statements in the nested transaction block become part of the parent
-
# transaction. For example, the following behavior may be surprising:
-
#
-
# User.transaction do
-
# User.create(username: 'Kotori')
-
# User.transaction do
-
# User.create(username: 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# creates both "Kotori" and "Nemu". Reason is the ActiveRecord::Rollback
-
# exception in the nested block does not issue a ROLLBACK. Since these exceptions
-
# are captured in transaction blocks, the parent block does not see it and the
-
# real transaction is committed.
-
#
-
# In order to get a ROLLBACK for the nested transaction you may ask for a real
-
# sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
-
# the database rolls back to the beginning of the sub-transaction without rolling
-
# back the parent transaction. If we add it to the previous example:
-
#
-
# User.transaction do
-
# User.create(username: 'Kotori')
-
# User.transaction(requires_new: true) do
-
# User.create(username: 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# only "Kotori" is created.
-
#
-
# Most databases don't support true nested transactions. At the time of
-
# writing, the only database that we're aware of that supports true nested
-
# transactions, is MS-SQL. Because of this, Active Record emulates nested
-
# transactions by using savepoints. See
-
# https://dev.mysql.com/doc/refman/en/savepoint.html
-
# for more information about savepoints.
-
#
-
# === \Callbacks
-
#
-
# There are two types of callbacks associated with committing and rolling back transactions:
-
# #after_commit and #after_rollback.
-
#
-
# #after_commit callbacks are called on every record saved or destroyed within a
-
# transaction immediately after the transaction is committed. #after_rollback callbacks
-
# are called on every record saved or destroyed within a transaction immediately after the
-
# transaction or savepoint is rolled back.
-
#
-
# These callbacks are useful for interacting with other systems since you will be guaranteed
-
# that the callback is only executed when the database is in a permanent state. For example,
-
# #after_commit is a good spot to put in a hook to clearing a cache since clearing it from
-
# within a transaction could trigger the cache to be regenerated before the database is updated.
-
#
-
# === Caveats
-
#
-
# If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested
-
# transactions blocks that are emulated with savepoints. That is, do not execute statements
-
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
-
# releases all savepoints upon executing a DDL operation. When +transaction+
-
# is finished and tries to release the savepoint it created earlier, a
-
# database error will occur because the savepoint has already been
-
# automatically released. The following example demonstrates the problem:
-
#
-
# Model.connection.transaction do # BEGIN
-
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
-
# Model.connection.create_table(...) # active_record_1 now automatically released
-
# end # RELEASE SAVEPOINT active_record_1
-
# # ^^^^ BOOM! database error!
-
# end
-
#
-
# Note that "TRUNCATE" is also a MySQL DDL statement!
-
3
module ClassMethods
-
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
-
3
def transaction(**options, &block)
-
3739
connection.transaction(**options, &block)
-
end
-
-
3
def before_commit(*args, &block) # :nodoc:
-
9
set_options_for_callbacks!(args)
-
9
set_callback(:before_commit, :before, *args, &block)
-
end
-
-
# This callback is called after a record has been created, updated, or destroyed.
-
#
-
# You can specify that the callback should only be fired by a certain action with
-
# the +:on+ option:
-
#
-
# after_commit :do_foo, on: :create
-
# after_commit :do_bar, on: :update
-
# after_commit :do_baz, on: :destroy
-
#
-
# after_commit :do_foo_bar, on: [:create, :update]
-
# after_commit :do_bar_baz, on: [:update, :destroy]
-
#
-
3
def after_commit(*args, &block)
-
49
set_options_for_callbacks!(args)
-
43
set_callback(:commit, :after, *args, &block)
-
end
-
-
# Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
-
3
def after_save_commit(*args, &block)
-
3
set_options_for_callbacks!(args, on: [ :create, :update ])
-
3
set_callback(:commit, :after, *args, &block)
-
end
-
-
# Shortcut for <tt>after_commit :hook, on: :create</tt>.
-
3
def after_create_commit(*args, &block)
-
12
set_options_for_callbacks!(args, on: :create)
-
12
set_callback(:commit, :after, *args, &block)
-
end
-
-
# Shortcut for <tt>after_commit :hook, on: :update</tt>.
-
3
def after_update_commit(*args, &block)
-
6
set_options_for_callbacks!(args, on: :update)
-
6
set_callback(:commit, :after, *args, &block)
-
end
-
-
# Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
-
3
def after_destroy_commit(*args, &block)
-
6
set_options_for_callbacks!(args, on: :destroy)
-
6
set_callback(:commit, :after, *args, &block)
-
end
-
-
# This callback is called after a create, update, or destroy are rolled back.
-
#
-
# Please check the documentation of #after_commit for options.
-
3
def after_rollback(*args, &block)
-
36
set_options_for_callbacks!(args)
-
30
set_callback(:rollback, :after, *args, &block)
-
end
-
-
3
private
-
3
def set_options_for_callbacks!(args, enforced_options = {})
-
121
options = args.extract_options!.merge!(enforced_options)
-
121
args << options
-
-
121
if options[:on]
-
75
fire_on = Array(options[:on])
-
75
assert_valid_transaction_action(fire_on)
-
63
options[:if] = Array(options[:if])
-
705
options[:if].unshift(-> { transaction_include_any_action?(fire_on) })
-
end
-
end
-
-
3
def assert_valid_transaction_action(actions)
-
75
if (actions - ACTIONS).any?
-
12
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
-
end
-
end
-
end
-
-
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
-
3
def transaction(**options, &block)
-
46
self.class.transaction(**options, &block)
-
end
-
-
3
def destroy #:nodoc:
-
2148
with_transaction_returning_status { super }
-
end
-
-
3
def save(**) #:nodoc:
-
16348
with_transaction_returning_status { super }
-
end
-
-
3
def save!(**) #:nodoc:
-
16494
with_transaction_returning_status { super }
-
end
-
-
3
def touch(*, **) #:nodoc:
-
1074
with_transaction_returning_status { super }
-
end
-
-
3
def before_committed! # :nodoc:
-
16238
_run_before_commit_callbacks
-
end
-
-
# Call the #after_commit callbacks.
-
#
-
# Ensure that it is not called if the object was never persisted (failed create),
-
# but call it after the commit of a destroyed object.
-
3
def committed!(should_run_callbacks: true) #:nodoc:
-
16269
force_clear_transaction_record_state
-
16269
if should_run_callbacks
-
16143
@_committed_already_called = true
-
16143
_run_commit_callbacks
-
end
-
ensure
-
16269
@_committed_already_called = @_trigger_update_callback = @_trigger_destroy_callback = false
-
end
-
-
# Call the #after_rollback callbacks. The +force_restore_state+ argument indicates if the record
-
# state should be rolled back to the beginning or just to the last savepoint.
-
3
def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
-
1185
if should_run_callbacks
-
438
_run_rollback_callbacks
-
end
-
ensure
-
1185
restore_transaction_record_state(force_restore_state)
-
1178
clear_transaction_record_state
-
1178
@_trigger_update_callback = @_trigger_destroy_callback = false if force_restore_state
-
end
-
-
# Executes +method+ within a transaction and captures its return value as a
-
# status flag. If the status is true the transaction is committed, otherwise
-
# a ROLLBACK is issued. In any case the status flag is returned.
-
#
-
# This method is available within the context of an ActiveRecord::Base
-
# instance.
-
3
def with_transaction_returning_status
-
18822
status = nil
-
18822
connection = self.class.connection
-
18822
ensure_finalize = !connection.transaction_open?
-
-
18822
connection.transaction do
-
18822
add_to_transaction(ensure_finalize || has_transactional_callbacks?)
-
18822
remember_transaction_record_state
-
-
18822
status = yield
-
18380
raise ActiveRecord::Rollback unless status
-
end
-
18402
status
-
end
-
-
3
def trigger_transactional_callbacks? # :nodoc:
-
17442
(@_new_record_before_last_commit || _trigger_update_callback) && persisted? ||
-
_trigger_destroy_callback && destroyed?
-
end
-
-
3
private
-
3
attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
-
-
# Save the new record state and id of a record so it can be restored later if a transaction fails.
-
3
def remember_transaction_record_state
-
18822
@_start_transaction_state ||= {
-
id: id,
-
new_record: @new_record,
-
previously_new_record: @previously_new_record,
-
destroyed: @destroyed,
-
attributes: @attributes,
-
frozen?: frozen?,
-
level: 0
-
}
-
18822
@_start_transaction_state[:level] += 1
-
-
18822
if _committed_already_called
-
9
@_new_record_before_last_commit = false
-
else
-
18813
@_new_record_before_last_commit = @_start_transaction_state[:new_record]
-
end
-
end
-
-
# Clear the new record state and id of a record.
-
3
def clear_transaction_record_state
-
1178
return unless @_start_transaction_state
-
1172
@_start_transaction_state[:level] -= 1
-
1172
force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
-
end
-
-
# Force to clear the transaction record state.
-
3
def force_clear_transaction_record_state
-
17318
@_start_transaction_state = nil
-
end
-
-
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
-
3
def restore_transaction_record_state(force_restore_state = false)
-
1185
if restore_state = @_start_transaction_state
-
1179
if force_restore_state || restore_state[:level] <= 1
-
1119
@new_record = restore_state[:new_record]
-
1119
@previously_new_record = restore_state[:previously_new_record]
-
1119
@destroyed = restore_state[:destroyed]
-
1119
@attributes = restore_state[:attributes].map do |attr|
-
12915
value = @attributes.fetch_value(attr.name)
-
12908
attr = attr.with_value_from_user(value) if attr.value != value
-
12908
attr
-
end
-
1112
@mutations_from_database = nil
-
1112
@mutations_before_last_save = nil
-
1112
if @attributes.fetch_value(@primary_key) != restore_state[:id]
-
170
@attributes.write_from_user(@primary_key, restore_state[:id])
-
end
-
1112
freeze if restore_state[:frozen?]
-
end
-
end
-
end
-
-
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
-
3
def transaction_include_any_action?(actions)
-
642
actions.any? do |action|
-
720
case action
-
when :create
-
351
persisted? && @_new_record_before_last_commit
-
when :update
-
210
!(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback
-
when :destroy
-
159
_trigger_destroy_callback
-
end
-
end
-
end
-
-
# Add the record to the current transaction so that the #after_rollback and #after_commit
-
# callbacks can be called.
-
3
def add_to_transaction(ensure_finalize = true)
-
19246
self.class.connection.add_transaction_record(self, ensure_finalize)
-
end
-
-
3
def has_transactional_callbacks?
-
17372
!_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Translation
-
3
include ActiveModel::Translation
-
-
# Set the lookup ancestors for ActiveModel.
-
3
def lookup_ancestors #:nodoc:
-
1312
klass = self
-
1312
classes = [klass]
-
1312
return classes if klass == ActiveRecord::Base
-
-
1309
while !klass.base_class?
-
493
classes << klass = klass.superclass
-
end
-
1309
classes
-
end
-
-
# Set the i18n scope to overwrite ActiveModel.
-
3
def i18n_scope #:nodoc:
-
1462
:activerecord
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_model/type"
-
-
3
require "active_record/type/internal/timezone"
-
-
3
require "active_record/type/date"
-
3
require "active_record/type/date_time"
-
3
require "active_record/type/decimal_without_scale"
-
3
require "active_record/type/json"
-
3
require "active_record/type/time"
-
3
require "active_record/type/text"
-
3
require "active_record/type/unsigned_integer"
-
-
3
require "active_record/type/serialized"
-
3
require "active_record/type/adapter_specific_registry"
-
-
3
require "active_record/type/type_map"
-
3
require "active_record/type/hash_lookup_type_map"
-
-
3
module ActiveRecord
-
3
module Type
-
3
@registry = AdapterSpecificRegistry.new
-
-
3
class << self
-
3
attr_accessor :registry # :nodoc:
-
3
delegate :add_modifier, to: :registry
-
-
# Add a new type to the registry, allowing it to be referenced as a
-
# symbol by {ActiveRecord::Base.attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
-
# If your type is only meant to be used with a specific database adapter, you can
-
# do so by passing <tt>adapter: :postgresql</tt>. If your type has the same
-
# name as a native type for the current adapter, an exception will be
-
# raised unless you specify an +:override+ option. <tt>override: true</tt> will
-
# cause your type to be used instead of the native type. <tt>override:
-
# false</tt> will cause the native type to be used over yours if one exists.
-
3
def register(type_name, klass = nil, **options, &block)
-
97
registry.register(type_name, klass, **options, &block)
-
end
-
-
3
def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc:
-
632
registry.lookup(*args, adapter: adapter, **kwargs)
-
end
-
-
3
def default_value # :nodoc:
-
28781
@default_value ||= Value.new
-
end
-
-
3
def adapter_name_from(model) # :nodoc:
-
# TODO: this shouldn't depend on a connection to the database
-
626
model.connection.adapter_name.downcase.to_sym
-
end
-
-
3
private
-
3
def current_adapter_name
-
18
adapter_name_from(ActiveRecord::Base)
-
end
-
end
-
-
3
BigInteger = ActiveModel::Type::BigInteger
-
3
Binary = ActiveModel::Type::Binary
-
3
Boolean = ActiveModel::Type::Boolean
-
3
Decimal = ActiveModel::Type::Decimal
-
3
Float = ActiveModel::Type::Float
-
3
Integer = ActiveModel::Type::Integer
-
3
ImmutableString = ActiveModel::Type::ImmutableString
-
3
String = ActiveModel::Type::String
-
3
Value = ActiveModel::Type::Value
-
-
3
register(:big_integer, Type::BigInteger, override: false)
-
3
register(:binary, Type::Binary, override: false)
-
3
register(:boolean, Type::Boolean, override: false)
-
3
register(:date, Type::Date, override: false)
-
3
register(:datetime, Type::DateTime, override: false)
-
3
register(:decimal, Type::Decimal, override: false)
-
3
register(:float, Type::Float, override: false)
-
3
register(:integer, Type::Integer, override: false)
-
3
register(:immutable_string, Type::ImmutableString, override: false)
-
3
register(:json, Type::Json, override: false)
-
3
register(:string, Type::String, override: false)
-
3
register(:text, Type::Text, override: false)
-
3
register(:time, Type::Time, override: false)
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_model/type/registry"
-
-
3
module ActiveRecord
-
# :stopdoc:
-
3
module Type
-
3
class AdapterSpecificRegistry < ActiveModel::Type::Registry
-
3
def add_modifier(options, klass, **args)
-
16
registrations << DecorationRegistration.new(options, klass, **args)
-
end
-
-
3
private
-
3
def registration_klass
-
148
Registration
-
end
-
-
3
def find_registration(symbol, *args, **kwargs)
-
registrations
-
16406
.select { |registration| registration.matches?(symbol, *args, **kwargs) }
-
739
.max
-
end
-
end
-
-
3
class Registration
-
3
def initialize(name, block, adapter: nil, override: nil)
-
148
@name = name
-
148
@block = block
-
148
@adapter = adapter
-
148
@override = override
-
end
-
-
3
def call(_registry, *args, adapter: nil, **kwargs)
-
707
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
-
83
block.call(*args, **kwargs)
-
else
-
624
block.call(*args)
-
end
-
end
-
-
3
def matches?(type_name, *args, **kwargs)
-
15554
type_name == name && matches_adapter?(**kwargs)
-
end
-
-
3
def <=>(other)
-
74
if conflicts_with?(other)
-
3
raise TypeConflictError.new("Type #{name} was registered for all
-
adapters, but shadows a native type with
-
the same name for #{other.adapter}".squish)
-
end
-
71
priority <=> other.priority
-
end
-
-
3
protected
-
3
attr_reader :name, :block, :adapter, :override
-
-
3
def priority
-
290
result = 0
-
290
if adapter
-
105
result |= 1
-
end
-
290
if override
-
6
result |= 2
-
end
-
290
result
-
end
-
-
3
def priority_except_adapter
-
148
priority & 0b111111100
-
end
-
-
3
private
-
3
def matches_adapter?(adapter: nil, **)
-
1744
(self.adapter.nil? || adapter == self.adapter)
-
end
-
-
3
def conflicts_with?(other)
-
74
same_priority_except_adapter?(other) &&
-
has_adapter_conflict?(other)
-
end
-
-
3
def same_priority_except_adapter?(other)
-
74
priority_except_adapter == other.priority_except_adapter
-
end
-
-
3
def has_adapter_conflict?(other)
-
48
(override.nil? && other.adapter) ||
-
48
(adapter && other.override.nil?)
-
end
-
end
-
-
3
class DecorationRegistration < Registration
-
3
def initialize(options, klass, adapter: nil)
-
16
@options = options
-
16
@klass = klass
-
16
@adapter = adapter
-
end
-
-
3
def call(registry, *args, **kwargs)
-
26
subtype = registry.lookup(*args, **kwargs.except(*options.keys))
-
26
klass.new(subtype)
-
end
-
-
3
def matches?(*args, **kwargs)
-
852
matches_adapter?(**kwargs) && matches_options?(**kwargs)
-
end
-
-
3
def priority
-
64
super | 4
-
end
-
-
3
private
-
3
attr_reader :options, :klass
-
-
3
def matches_options?(**kwargs)
-
515
options.all? do |key, value|
-
515
kwargs[key] == value
-
end
-
end
-
end
-
end
-
-
3
class TypeConflictError < StandardError
-
end
-
# :startdoc:
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class Date < ActiveModel::Type::Date
-
3
include Internal::Timezone
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class DateTime < ActiveModel::Type::DateTime
-
3
include Internal::Timezone
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class DecimalWithoutScale < ActiveModel::Type::BigInteger # :nodoc:
-
3
def type
-
263
:decimal
-
end
-
-
3
def type_cast_for_schema(value)
-
value.to_s.inspect
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class HashLookupTypeMap < TypeMap # :nodoc:
-
3
def alias_type(type, alias_type)
-
37160
register_type(type) { |_, *args| lookup(alias_type, *args) }
-
end
-
-
3
def key?(key)
-
757558
@mapping.key?(key)
-
end
-
-
3
def keys
-
632
@mapping.keys
-
end
-
-
3
private
-
3
def perform_fetch(type, *args, &block)
-
19053
@mapping.fetch(type, block).call(type, *args)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
module Internal
-
3
module Timezone
-
3
def is_utc?
-
76834
ActiveRecord::Base.default_timezone == :utc
-
end
-
-
3
def default_timezone
-
147
ActiveRecord::Base.default_timezone
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class Json < ActiveModel::Type::Value
-
3
include ActiveModel::Type::Helpers::Mutable
-
-
3
def type
-
194
:json
-
end
-
-
3
def deserialize(value)
-
1079
return value unless value.is_a?(::String)
-
639
ActiveSupport::JSON.decode(value) rescue nil
-
end
-
-
3
def serialize(value)
-
1667
ActiveSupport::JSON.encode(value) unless value.nil?
-
end
-
-
3
def changed_in_place?(raw_old_value, new_value)
-
81
deserialize(raw_old_value) != new_value
-
end
-
-
3
def accessor
-
77
ActiveRecord::Store::StringKeyedHashAccessor
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
-
3
undef to_yaml if method_defined?(:to_yaml)
-
-
3
include ActiveModel::Type::Helpers::Mutable
-
-
3
attr_reader :subtype, :coder
-
-
3
def initialize(subtype, coder)
-
491
@subtype = subtype
-
491
@coder = coder
-
491
super(subtype)
-
end
-
-
3
def deserialize(value)
-
4557
if default_value?(value)
-
1347
value
-
else
-
3210
coder.load(super)
-
end
-
end
-
-
3
def serialize(value)
-
4367
return if value.nil?
-
3714
unless default_value?(value)
-
3402
super coder.dump(value)
-
end
-
end
-
-
3
def inspect
-
Kernel.instance_method(:inspect).bind(self).call
-
end
-
-
3
def changed_in_place?(raw_old_value, value)
-
410
return false if value.nil?
-
365
raw_new_value = encoded(value)
-
365
raw_old_value.nil? != raw_new_value.nil? ||
-
subtype.changed_in_place?(raw_old_value, raw_new_value)
-
end
-
-
3
def accessor
-
1080
ActiveRecord::Store::IndifferentHashAccessor
-
end
-
-
3
def assert_valid_value(value)
-
612
if coder.respond_to?(:assert_valid_value)
-
561
coder.assert_valid_value(value, action: "serialize")
-
end
-
end
-
-
3
def force_equality?(value)
-
27
coder.respond_to?(:object_class) && value.is_a?(coder.object_class)
-
end
-
-
3
private
-
3
def default_value?(value)
-
8636
value == coder.load(nil)
-
end
-
-
3
def encoded(value)
-
365
unless default_value?(value)
-
347
coder.dump(value)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class Text < ActiveModel::Type::String # :nodoc:
-
3
def type
-
1226
:text
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class Time < ActiveModel::Type::Time
-
3
include Internal::Timezone
-
-
3
class Value < DelegateClass(::Time) # :nodoc:
-
end
-
-
3
def serialize(value)
-
2240
case value = super
-
when ::Time
-
910
Value.new(value)
-
else
-
1330
value
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "concurrent/map"
-
-
3
module ActiveRecord
-
3
module Type
-
3
class TypeMap # :nodoc:
-
3
def initialize
-
553
@mapping = {}
-
553
@cache = Concurrent::Map.new do |h, key|
-
15178
h.fetch_or_store(key, Concurrent::Map.new)
-
end
-
end
-
-
3
def lookup(lookup_key, *args)
-
1002922
fetch(lookup_key, *args) { Type.default_value }
-
end
-
-
3
def fetch(lookup_key, *args, &block)
-
1153086
@cache[lookup_key].fetch_or_store(args) do
-
19936
perform_fetch(lookup_key, *args, &block)
-
end
-
end
-
-
3
def register_type(key, value = nil, &block)
-
87619
raise ::ArgumentError unless value || block
-
87616
@cache.clear
-
-
87616
if block
-
65346
@mapping[key] = block
-
else
-
25950
@mapping[key] = proc { value }
-
end
-
end
-
-
3
def alias_type(key, target_key)
-
606
register_type(key) do |sql_type, *args|
-
64
metadata = sql_type[/\(.*\)/, 0]
-
64
lookup("#{target_key}#{metadata}", *args)
-
end
-
end
-
-
3
def clear
-
226
@mapping.clear
-
end
-
-
3
private
-
3
def perform_fetch(lookup_key, *args)
-
883
matching_pair = @mapping.reverse_each.detect do |key, _|
-
9164
key === lookup_key
-
end
-
-
883
if matching_pair
-
859
matching_pair.last.call(lookup_key, *args)
-
else
-
24
yield lookup_key, *args
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Type
-
3
class UnsignedInteger < ActiveModel::Type::Integer # :nodoc:
-
3
private
-
3
def max_value
-
638
super * 2
-
end
-
-
3
def min_value
-
638
0
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_record/type_caster/map"
-
3
require "active_record/type_caster/connection"
-
-
3
module ActiveRecord
-
3
module TypeCaster # :nodoc:
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module TypeCaster
-
3
class Connection # :nodoc:
-
3
def initialize(klass, table_name)
-
554
@klass = klass
-
554
@table_name = table_name
-
end
-
-
3
def type_cast_for_database(attr_name, value)
-
type = type_for_attribute(attr_name)
-
type.serialize(value)
-
end
-
-
3
def type_for_attribute(attr_name)
-
896
schema_cache = connection.schema_cache
-
-
896
if schema_cache.data_source_exists?(table_name)
-
767
column = schema_cache.columns_hash(table_name)[attr_name.to_s]
-
767
type = connection.lookup_cast_type_from_column(column) if column
-
end
-
-
896
type || Type.default_value
-
end
-
-
3
delegate :connection, to: :@klass, private: true
-
-
3
private
-
3
attr_reader :table_name
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module TypeCaster
-
3
class Map # :nodoc:
-
3
def initialize(klass)
-
2548
@klass = klass
-
end
-
-
3
def type_cast_for_database(attr_name, value)
-
15
type = type_for_attribute(attr_name)
-
15
type.serialize(value)
-
end
-
-
3
def type_for_attribute(name)
-
156939
klass.type_for_attribute(name)
-
end
-
-
3
private
-
3
attr_reader :klass
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
# = Active Record \RecordInvalid
-
#
-
# Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
-
# {ActiveRecord::Base#create!}[rdoc-ref:Persistence::ClassMethods#create!] when the record is invalid.
-
# Use the #record method to retrieve the record which did not validate.
-
#
-
# begin
-
# complex_operation_that_internally_calls_save!
-
# rescue ActiveRecord::RecordInvalid => invalid
-
# puts invalid.record.errors
-
# end
-
3
class RecordInvalid < ActiveRecordError
-
3
attr_reader :record
-
-
3
def initialize(record = nil)
-
153
if record
-
150
@record = record
-
150
errors = @record.errors.full_messages.join(", ")
-
150
message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid")
-
else
-
3
message = "Record invalid"
-
end
-
-
153
super(message)
-
end
-
end
-
-
# = Active Record \Validations
-
#
-
# Active Record includes the majority of its validations from ActiveModel::Validations
-
# all of which accept the <tt>:on</tt> argument to define the context where the
-
# validations are active. Active Record will always supply either the context of
-
# <tt>:create</tt> or <tt>:update</tt> dependent on whether the model is a
-
# {new_record?}[rdoc-ref:Persistence#new_record?].
-
3
module Validations
-
3
extend ActiveSupport::Concern
-
3
include ActiveModel::Validations
-
-
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
-
# The validation context can be changed by passing <tt>context: context</tt>.
-
# The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced
-
# with this when the validations module is mixed in, which it is by default.
-
3
def save(**options)
-
8174
perform_validations(options) ? super : false
-
end
-
-
# Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but
-
# will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
-
3
def save!(**options)
-
8247
perform_validations(options) ? super : raise_validation_error
-
end
-
-
# Runs all the validations within the specified context. Returns +true+ if
-
# no errors are found, +false+ otherwise.
-
#
-
# Aliased as #validate.
-
#
-
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
-
# {new_record?}[rdoc-ref:Persistence#new_record?] is +true+, and to <tt>:update</tt> if it is not.
-
#
-
# \Validations with no <tt>:on</tt> option will run no matter the context. \Validations with
-
# some <tt>:on</tt> option will only run in the specified context.
-
3
def valid?(context = nil)
-
18068
context ||= default_validation_context
-
18068
output = super(context)
-
18065
errors.empty? && output
-
end
-
-
3
alias_method :validate, :valid?
-
-
3
private
-
3
def default_validation_context
-
18020
new_record? ? :create : :update
-
end
-
-
3
def raise_validation_error
-
104
raise(RecordInvalid.new(self))
-
end
-
-
3
def perform_validations(options = {})
-
16421
options[:validate] == false || valid?(options[:context])
-
end
-
end
-
end
-
-
3
require "active_record/validations/associated"
-
3
require "active_record/validations/uniqueness"
-
3
require "active_record/validations/presence"
-
3
require "active_record/validations/absence"
-
3
require "active_record/validations/length"
-
3
require "active_record/validations/numericality"
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Validations
-
3
class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
-
3
def validate_each(record, attribute, association_or_value)
-
33
if record.class._reflect_on_association(attribute)
-
18
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
-
end
-
33
super
-
end
-
end
-
-
3
module ClassMethods
-
# Validates that the specified attributes are not present (as defined by
-
# Object#present?). If the attribute is an association, the associated object
-
# is considered absent if it was marked for destruction.
-
#
-
# See ActiveModel::Validations::HelperMethods.validates_absence_of for more information.
-
3
def validates_absence_of(*attr_names)
-
15
validates_with AbsenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Validations
-
3
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
-
3
def validate_each(record, attribute, value)
-
223
if Array(value).reject { |r| valid_object?(r) }.any?
-
36
record.errors.add(attribute, :invalid, **options.merge(value: value))
-
end
-
end
-
-
3
private
-
3
def valid_object?(record)
-
66
(record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
-
end
-
end
-
-
3
module ClassMethods
-
# Validates whether the associated object or objects are all valid.
-
# Works with any kind of association.
-
#
-
# class Book < ActiveRecord::Base
-
# has_many :pages
-
# belongs_to :library
-
#
-
# validates_associated :pages, :library
-
# end
-
#
-
# WARNING: This validation must not be used on both ends of an association.
-
# Doing so will lead to a circular dependency and cause infinite recursion.
-
#
-
# NOTE: This validation will not fail if the association hasn't been
-
# assigned. If you want to ensure that the association is both present and
-
# guaranteed to be valid, you also need to use
-
# {validates_presence_of}[rdoc-ref:Validations::ClassMethods#validates_presence_of].
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default +nil+. You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
3
def validates_associated(*attr_names)
-
36
validates_with AssociatedValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Validations
-
3
class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
-
3
def validate_each(record, attribute, association_or_value)
-
323
if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
-
36
association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
-
end
-
323
super
-
end
-
end
-
-
3
module ClassMethods
-
# Validates that the specified attributes match the length restrictions supplied.
-
# If the attribute is an association, records that are marked for destruction are not counted.
-
#
-
# See ActiveModel::Validations::HelperMethods.validates_length_of for more information.
-
3
def validates_length_of(*attr_names)
-
24
validates_with LengthValidator, _merge_attributes(attr_names)
-
end
-
-
3
alias_method :validates_size_of, :validates_length_of
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Validations
-
3
class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
-
3
def validate_each(record, attribute, value, precision: nil, scale: nil)
-
98
precision = [column_precision_for(record, attribute) || BigDecimal.double_fig, BigDecimal.double_fig].min
-
98
scale = column_scale_for(record, attribute)
-
98
super(record, attribute, value, precision: precision, scale: scale)
-
end
-
-
3
private
-
3
def column_precision_for(record, attribute)
-
98
record.class.type_for_attribute(attribute.to_s)&.precision
-
end
-
-
3
def column_scale_for(record, attribute)
-
98
record.class.type_for_attribute(attribute.to_s)&.scale
-
end
-
end
-
-
3
module ClassMethods
-
# Validates whether the value of the specified attribute is numeric by
-
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
-
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
-
# (if <tt>only_integer</tt> is set to +true+). Kernel.Float precision
-
# defaults to the column's precision value or 15.
-
#
-
# See ActiveModel::Validations::HelperMethods.validates_numericality_of for more information.
-
3
def validates_numericality_of(*attr_names)
-
39
validates_with NumericalityValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Validations
-
3
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
-
3
def validate_each(record, attribute, association_or_value)
-
5689
if record.class._reflect_on_association(attribute)
-
84
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
-
end
-
5689
super
-
end
-
end
-
-
3
module ClassMethods
-
# Validates that the specified attributes are not blank (as defined by
-
# Object#blank?), and, if the attribute is an association, that the
-
# associated object is not marked for destruction. Happens by default
-
# on save.
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :face
-
# validates_presence_of :face
-
# end
-
#
-
# The face attribute must be in the object and it cannot be blank or marked
-
# for destruction.
-
#
-
# If you want to validate the presence of a boolean field (where the real values
-
# are true and false), you will want to use
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
-
#
-
# This is due to the way Object#blank? handles boolean values:
-
# <tt>false.blank? # => true</tt>.
-
#
-
# This validator defers to the Active Model validation for presence, adding the
-
# check to see that an associated object is not marked for destruction. This
-
# prevents the parent object from validating successfully and saving, which then
-
# deletes the associated object, thus putting the parent object into an invalid
-
# state.
-
#
-
# NOTE: This validation will not fail while using it with an association
-
# if the latter was assigned but not valid. If you want to ensure that
-
# it is both present and valid, you also need to use
-
# {validates_associated}[rdoc-ref:Validations::ClassMethods#validates_associated].
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default +nil+. You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
-
# the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
-
# <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
-
# or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
-
# See ActiveModel::Validations#validates! for more information.
-
3
def validates_presence_of(*attr_names)
-
171
validates_with PresenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActiveRecord
-
3
module Validations
-
3
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
-
3
def initialize(options)
-
140
if options[:conditions] && !options[:conditions].respond_to?(:call)
-
3
raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
-
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
-
end
-
164
unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) }
-
3
raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \
-
"Pass a symbol or an array of symbols instead: `scope: :user_id`"
-
end
-
134
super
-
134
@klass = options[:class]
-
end
-
-
3
def validate_each(record, attribute, value)
-
523
finder_class = find_finder_class_for(record)
-
523
value = map_enum_attribute(finder_class, attribute, value)
-
-
523
relation = build_relation(finder_class, attribute, value)
-
523
if record.persisted?
-
85
if finder_class.primary_key
-
82
relation = relation.where.not(finder_class.primary_key => record.id_in_database)
-
else
-
3
raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.")
-
end
-
end
-
520
relation = scope_relation(record, relation)
-
520
relation = relation.merge(options[:conditions]) if options[:conditions]
-
-
520
if relation.exists?
-
177
error_options = options.except(:case_sensitive, :scope, :conditions)
-
177
error_options[:value] = value
-
-
177
record.errors.add(attribute, :taken, **error_options)
-
end
-
end
-
-
3
private
-
# The check for an existing value should be run from a class that
-
# isn't abstract. This means working down from the current class
-
# (self), to the first non-abstract class. Since classes don't know
-
# their subclasses, we have to build the hierarchy between self and
-
# the record's class.
-
3
def find_finder_class_for(record)
-
523
class_hierarchy = [record.class]
-
-
523
while class_hierarchy.first != @klass
-
66
class_hierarchy.unshift(class_hierarchy.first.superclass)
-
end
-
-
1070
class_hierarchy.detect { |klass| !klass.abstract_class? }
-
end
-
-
3
def build_relation(klass, attribute, value)
-
523
relation = klass.unscoped
-
523
comparison = relation.bind_attribute(attribute, value) do |attr, bind|
-
523
return relation.none! if bind.unboundable?
-
-
521
if !options.key?(:case_sensitive) || bind.nil?
-
427
klass.connection.default_uniqueness_comparison(attr, bind, klass)
-
94
elsif options[:case_sensitive]
-
33
klass.connection.case_sensitive_comparison(attr, bind)
-
else
-
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
-
61
klass.connection.case_insensitive_comparison(attr, bind)
-
end
-
end
-
-
521
relation.where!(comparison)
-
end
-
-
3
def scope_relation(record, relation)
-
520
Array(options[:scope]).each do |scope_item|
-
154
scope_value = if record.class._reflect_on_association(scope_item)
-
28
record.association(scope_item).reader
-
else
-
126
record.read_attribute(scope_item)
-
end
-
154
relation = relation.where(scope_item => scope_value)
-
end
-
-
520
relation
-
end
-
-
3
def map_enum_attribute(klass, attribute, value)
-
523
mapping = klass.defined_enums[attribute.to_s]
-
523
value = mapping[value] if value && mapping
-
523
value
-
end
-
end
-
-
3
module ClassMethods
-
# Validates whether the value of the specified attributes are unique
-
# across the system. Useful for making sure that only one user
-
# can be named "davidhh".
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name
-
# end
-
#
-
# It can also validate whether the value of the specified attributes are
-
# unique based on a <tt>:scope</tt> parameter:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name, scope: :account_id
-
# end
-
#
-
# Or even multiple scope parameters. For example, making sure that a
-
# teacher can only be on the schedule once per semester for a particular
-
# class.
-
#
-
# class TeacherSchedule < ActiveRecord::Base
-
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
-
# end
-
#
-
# It is also possible to limit the uniqueness constraint to a set of
-
# records matching certain conditions. In this example archived articles
-
# are not being taken into consideration when validating uniqueness
-
# of the title attribute:
-
#
-
# class Article < ActiveRecord::Base
-
# validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
-
# end
-
#
-
# When the record is created, a check is performed to make sure that no
-
# record exists in the database with the given value for the specified
-
# attribute (that maps to a column). When the record is updated,
-
# the same check is made but disregarding the record itself.
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - Specifies a custom error message (default is:
-
# "has already been taken").
-
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
-
# the uniqueness constraint.
-
# * <tt>:conditions</tt> - Specify the conditions to be included as a
-
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
-
# (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
-
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
-
# non-text columns (+true+ by default).
-
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
-
# attribute is blank (default is +false+).
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
#
-
# === Concurrency and integrity
-
#
-
# Using this validation method in conjunction with
-
# {ActiveRecord::Base#save}[rdoc-ref:Persistence#save]
-
# does not guarantee the absence of duplicate record insertions, because
-
# uniqueness checks on the application level are inherently prone to race
-
# conditions. For example, suppose that two users try to post a Comment at
-
# the same time, and a Comment's title must be unique. At the database-level,
-
# the actions performed by these users could be interleaved in the following manner:
-
#
-
# User 1 | User 2
-
# ------------------------------------+--------------------------------------
-
# # User 1 checks whether there's |
-
# # already a comment with the title |
-
# # 'My Post'. This is not the case. |
-
# SELECT * FROM comments |
-
# WHERE title = 'My Post' |
-
# |
-
# | # User 2 does the same thing and also
-
# | # infers that their title is unique.
-
# | SELECT * FROM comments
-
# | WHERE title = 'My Post'
-
# |
-
# # User 1 inserts their comment. |
-
# INSERT INTO comments |
-
# (title, content) VALUES |
-
# ('My Post', 'hi!') |
-
# |
-
# | # User 2 does the same thing.
-
# | INSERT INTO comments
-
# | (title, content) VALUES
-
# | ('My Post', 'hello!')
-
# |
-
# | # ^^^^^^
-
# | # Boom! We now have a duplicate
-
# | # title!
-
#
-
# The best way to work around this problem is to add a unique index to the database table using
-
# {connection.add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index].
-
# In the rare case that a race condition occurs, the database will guarantee
-
# the field's uniqueness.
-
#
-
# When the database catches such a duplicate insertion,
-
# {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveRecord::StatementInvalid
-
# exception. You can either choose to let this error propagate (which
-
# will result in the default Rails exception page being shown), or you
-
# can catch it and restart the transaction (e.g. by telling the user
-
# that the title already exists, and asking them to re-enter the title).
-
# This technique is also known as
-
# {optimistic concurrency control}[https://en.wikipedia.org/wiki/Optimistic_concurrency_control].
-
#
-
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
-
# constraint errors from other types of database errors by throwing an
-
# ActiveRecord::RecordNotUnique exception. For other adapters you will
-
# have to parse the (database-specific) exception message to detect such
-
# a case.
-
#
-
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
-
#
-
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
-
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
-
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
-
3
def validates_uniqueness_of(*attr_names)
-
124
validates_with UniquenessValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require_relative "gem_version"
-
-
3
module ActiveRecord
-
# Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt>
-
3
def self.version
-
gem_version
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "arel/errors"
-
-
3
require "arel/crud"
-
3
require "arel/factory_methods"
-
-
3
require "arel/expressions"
-
3
require "arel/predications"
-
3
require "arel/window_predications"
-
3
require "arel/math"
-
3
require "arel/alias_predication"
-
3
require "arel/order_predications"
-
3
require "arel/table"
-
3
require "arel/attributes/attribute"
-
-
3
require "arel/visitors"
-
3
require "arel/collectors/sql_string"
-
-
3
require "arel/tree_manager"
-
3
require "arel/insert_manager"
-
3
require "arel/select_manager"
-
3
require "arel/update_manager"
-
3
require "arel/delete_manager"
-
3
require "arel/nodes"
-
-
3
module Arel
-
3
VERSION = "10.0.0"
-
-
# Wrap a known-safe SQL string for passing to query methods, e.g.
-
#
-
# Post.order(Arel.sql("length(title)")).last
-
#
-
# Great caution should be taken to avoid SQL injection vulnerabilities.
-
# This method should not be used with unsafe values such as request
-
# parameters or model attributes.
-
3
def self.sql(raw_sql)
-
36315
Arel::Nodes::SqlLiteral.new raw_sql
-
end
-
-
3
def self.star # :nodoc:
-
30348
sql "*"
-
end
-
-
3
def self.arel_node?(value) # :nodoc:
-
14882
value.is_a?(Arel::Nodes::Node) || value.is_a?(Arel::Attribute) || value.is_a?(Arel::Nodes::SqlLiteral)
-
end
-
-
3
def self.fetch_attribute(value, &block) # :nodoc:
-
30580
unless String === value
-
30058
value.fetch_attribute(&block)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module AliasPredication
-
3
def as(other)
-
10838
Nodes::As.new self, Nodes::SqlLiteral.new(other)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Attributes
-
3
class Attribute < Struct.new :relation, :name
-
3
include Arel::Expressions
-
3
include Arel::Predications
-
3
include Arel::AliasPredication
-
3
include Arel::OrderPredications
-
3
include Arel::Math
-
-
3
def type_caster
-
6155
relation.type_for_attribute(name)
-
end
-
-
###
-
# Create a node for lowering this attribute
-
3
def lower
-
59
relation.lower self
-
end
-
-
3
def type_cast_for_database(value)
-
21
relation.type_cast_for_database(name, value)
-
end
-
-
3
def able_to_type_cast?
-
579
relation.able_to_type_cast?
-
end
-
end
-
-
3
class String < Attribute; end
-
3
class Time < Attribute; end
-
3
class Boolean < Attribute; end
-
3
class Decimal < Attribute; end
-
3
class Float < Attribute; end
-
3
class Integer < Attribute; end
-
3
class Undefined < Attribute; end
-
end
-
-
3
Attribute = Attributes::Attribute
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Collectors
-
3
class Bind
-
3
def initialize
-
53134
@binds = []
-
end
-
-
3
def <<(str)
-
983723
self
-
end
-
-
3
def add_bind(bind)
-
103661
@binds << bind
-
103661
self
-
end
-
-
3
def add_binds(binds)
-
3868
@binds.concat binds
-
3868
self
-
end
-
-
3
def value
-
53134
@binds
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Collectors
-
3
class Composite
-
3
attr_accessor :preparable
-
-
3
def initialize(left, right)
-
53128
@left = left
-
53128
@right = right
-
end
-
-
3
def <<(str)
-
983645
left << str
-
983645
right << str
-
983645
self
-
end
-
-
3
def add_bind(bind, &block)
-
103649
left.add_bind bind, &block
-
103649
right.add_bind bind, &block
-
103649
self
-
end
-
-
3
def add_binds(binds, &block)
-
3868
left.add_binds(binds, &block)
-
3868
right.add_binds(binds, &block)
-
3868
self
-
end
-
-
3
def value
-
53128
[left.value, right.value]
-
end
-
-
3
private
-
3
attr_reader :left, :right
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Collectors
-
3
class PlainString
-
3
def initialize
-
209482
@str = +""
-
end
-
-
3
def value
-
209368
@str
-
end
-
-
3
def <<(str)
-
5117566
@str << str
-
5117566
self
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "arel/collectors/plain_string"
-
-
3
module Arel # :nodoc: all
-
3
module Collectors
-
3
class SQLString < PlainString
-
3
attr_accessor :preparable
-
-
3
def initialize(*)
-
209377
super
-
209377
@bind_index = 1
-
end
-
-
3
def add_bind(bind)
-
103973
self << yield(@bind_index)
-
103973
@bind_index += 1
-
103973
self
-
end
-
-
3
def add_binds(binds, &block)
-
3898
self << (@bind_index...@bind_index += binds.size).map(&block).join(", ")
-
3898
self
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Collectors
-
3
class SubstituteBinds
-
3
attr_accessor :preparable
-
-
3
def initialize(quoter, delegate_collector)
-
504
@quoter = quoter
-
504
@delegate = delegate_collector
-
end
-
-
3
def <<(str)
-
11706
delegate << str
-
11706
self
-
end
-
-
3
def add_bind(bind)
-
363
bind = bind.value_for_database if bind.respond_to?(:value_for_database)
-
363
self << quoter.quote(bind)
-
end
-
-
3
def add_binds(binds)
-
462967
self << binds.map { |bind| quoter.quote(bind) }.join(", ")
-
end
-
-
3
def value
-
504
delegate.value
-
end
-
-
3
private
-
3
attr_reader :quoter, :delegate
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
###
-
# FIXME hopefully we can remove this
-
3
module Crud
-
3
def compile_update(values, pk)
-
2812
um = UpdateManager.new
-
-
2812
if Nodes::SqlLiteral === values
-
9
relation = @ctx.from
-
else
-
2803
relation = values.first.first.relation
-
end
-
2812
um.key = pk
-
2812
um.table relation
-
2812
um.set values
-
2812
um.take @ast.limit.expr if @ast.limit
-
2812
um.order(*@ast.orders)
-
2812
um.wheres = @ctx.wheres
-
2812
um
-
end
-
-
3
def compile_insert(values)
-
12443
im = create_insert
-
12443
im.insert values
-
12443
im
-
end
-
-
3
def create_insert
-
12446
InsertManager.new
-
end
-
-
3
def compile_delete
-
9
dm = DeleteManager.new
-
9
dm.take @ast.limit.expr if @ast.limit
-
9
dm.wheres = @ctx.wheres
-
9
dm.from @ctx.froms
-
9
dm
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
class DeleteManager < Arel::TreeManager
-
3
include TreeManager::StatementMethods
-
-
3
def initialize
-
3363
super
-
3363
@ast = Nodes::DeleteStatement.new
-
3363
@ctx = @ast
-
end
-
-
3
def from(relation)
-
3357
@ast.relation = relation
-
3357
self
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
class ArelError < StandardError
-
end
-
-
3
class EmptyJoinError < ArelError
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Expressions
-
3
def count(distinct = false)
-
3999
Nodes::Count.new [self], distinct
-
end
-
-
3
def sum
-
175
Nodes::Sum.new [self]
-
end
-
-
3
def maximum
-
127
Nodes::Max.new [self]
-
end
-
-
3
def minimum
-
105
Nodes::Min.new [self]
-
end
-
-
3
def average
-
75
Nodes::Avg.new [self]
-
end
-
-
3
def extract(field)
-
21
Nodes::Extract.new [self], field
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
###
-
# Methods for creating various nodes
-
3
module FactoryMethods
-
3
def create_true
-
6
Arel::Nodes::True.new
-
end
-
-
3
def create_false
-
6
Arel::Nodes::False.new
-
end
-
-
3
def create_table_alias(relation, name)
-
354
Nodes::TableAlias.new(relation, name)
-
end
-
-
3
def create_join(to, constraint = nil, klass = Nodes::InnerJoin)
-
90
klass.new(to, constraint)
-
end
-
-
3
def create_string_join(to)
-
3
create_join to, nil, Nodes::StringJoin
-
end
-
-
3
def create_and(clauses)
-
12
Nodes::And.new clauses
-
end
-
-
3
def create_on(expr)
-
18
Nodes::On.new expr
-
end
-
-
3
def grouping(expr)
-
354
Nodes::Grouping.new expr
-
end
-
-
###
-
# Create a LOWER() function
-
3
def lower(column)
-
118
Nodes::NamedFunction.new "LOWER", [Nodes.build_quoted(column)]
-
end
-
-
3
def coalesce(*exprs)
-
1049
Nodes::NamedFunction.new "COALESCE", exprs
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
class InsertManager < Arel::TreeManager
-
3
def initialize
-
165953
super
-
165953
@ast = Nodes::InsertStatement.new
-
end
-
-
3
def into(table)
-
154780
@ast.relation = table
-
154780
self
-
end
-
-
385585
def columns; @ast.columns end
-
153471
def values=(val); @ast.values = val; end
-
-
3
def select(select)
-
3
@ast.select = select
-
end
-
-
3
def insert(fields)
-
12467
return if fields.empty?
-
-
12464
if String === fields
-
1297
@ast.values = Nodes::SqlLiteral.new(fields)
-
else
-
11167
@ast.relation ||= fields.first.first.relation
-
-
11167
values = []
-
-
11167
fields.each do |column, value|
-
31076
@ast.columns << column
-
31076
values << value
-
end
-
11167
@ast.values = create_values(values)
-
end
-
12464
self
-
end
-
-
3
def create_values(values)
-
11170
Nodes::ValuesList.new([values])
-
end
-
-
3
def create_values_list(rows)
-
153459
Nodes::ValuesList.new(rows)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Math
-
3
def *(other)
-
18
Arel::Nodes::Multiplication.new(self, other)
-
end
-
-
3
def +(other)
-
844
Arel::Nodes::Grouping.new(Arel::Nodes::Addition.new(self, other))
-
end
-
-
3
def -(other)
-
241
Arel::Nodes::Grouping.new(Arel::Nodes::Subtraction.new(self, other))
-
end
-
-
3
def /(other)
-
18
Arel::Nodes::Division.new(self, other)
-
end
-
-
3
def &(other)
-
18
Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(self, other))
-
end
-
-
3
def |(other)
-
18
Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(self, other))
-
end
-
-
3
def ^(other)
-
18
Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseXor.new(self, other))
-
end
-
-
3
def <<(other)
-
18
Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftLeft.new(self, other))
-
end
-
-
3
def >>(other)
-
18
Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftRight.new(self, other))
-
end
-
-
3
def ~@
-
3
Arel::Nodes::BitwiseNot.new(self)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
# node
-
3
require "arel/nodes/node"
-
3
require "arel/nodes/node_expression"
-
3
require "arel/nodes/select_statement"
-
3
require "arel/nodes/select_core"
-
3
require "arel/nodes/insert_statement"
-
3
require "arel/nodes/update_statement"
-
3
require "arel/nodes/bind_param"
-
-
# terminal
-
-
3
require "arel/nodes/terminal"
-
3
require "arel/nodes/true"
-
3
require "arel/nodes/false"
-
-
# unary
-
3
require "arel/nodes/unary"
-
3
require "arel/nodes/grouping"
-
3
require "arel/nodes/homogeneous_in"
-
3
require "arel/nodes/ordering"
-
3
require "arel/nodes/ascending"
-
3
require "arel/nodes/descending"
-
3
require "arel/nodes/unqualified_column"
-
3
require "arel/nodes/with"
-
-
# binary
-
3
require "arel/nodes/binary"
-
3
require "arel/nodes/equality"
-
3
require "arel/nodes/in"
-
3
require "arel/nodes/join_source"
-
3
require "arel/nodes/delete_statement"
-
3
require "arel/nodes/table_alias"
-
3
require "arel/nodes/infix_operation"
-
3
require "arel/nodes/unary_operation"
-
3
require "arel/nodes/over"
-
3
require "arel/nodes/matches"
-
3
require "arel/nodes/regexp"
-
-
# nary
-
3
require "arel/nodes/and"
-
-
# function
-
# FIXME: Function + Alias can be rewritten as a Function and Alias node.
-
# We should make Function a Unary node and deprecate the use of "aliaz"
-
3
require "arel/nodes/function"
-
3
require "arel/nodes/count"
-
3
require "arel/nodes/extract"
-
3
require "arel/nodes/values_list"
-
3
require "arel/nodes/named_function"
-
-
# windows
-
3
require "arel/nodes/window"
-
-
# conditional expressions
-
3
require "arel/nodes/case"
-
-
# joins
-
3
require "arel/nodes/full_outer_join"
-
3
require "arel/nodes/inner_join"
-
3
require "arel/nodes/outer_join"
-
3
require "arel/nodes/right_outer_join"
-
3
require "arel/nodes/string_join"
-
-
3
require "arel/nodes/comment"
-
-
3
require "arel/nodes/sql_literal"
-
-
3
require "arel/nodes/casted"
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class And < Arel::Nodes::NodeExpression
-
3
attr_reader :children
-
-
3
def initialize(children)
-
12718
super()
-
12718
@children = children
-
end
-
-
3
def left
-
3
children.first
-
end
-
-
3
def right
-
3
children[1]
-
end
-
-
3
def hash
-
12
children.hash
-
end
-
-
3
def eql?(other)
-
51
self.class == other.class &&
-
self.children == other.children
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Ascending < Ordering
-
3
def reverse
-
264
Descending.new(expr)
-
end
-
-
3
def direction
-
3
:asc
-
end
-
-
3
def ascending?
-
9
true
-
end
-
-
3
def descending?
-
3
false
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Binary < Arel::Nodes::NodeExpression
-
3
attr_accessor :left, :right
-
-
3
def initialize(left, right)
-
139957
super()
-
139957
@left = left
-
139957
@right = right
-
end
-
-
3
def initialize_copy(other)
-
30
super
-
30
@left = @left.clone if @left
-
30
@right = @right.clone if @right
-
end
-
-
3
def hash
-
135
[self.class, @left, @right].hash
-
end
-
-
3
def eql?(other)
-
52645
self.class == other.class &&
-
self.left == other.left &&
-
self.right == other.right
-
end
-
3
alias :== :eql?
-
end
-
-
3
module FetchAttribute
-
3
def fetch_attribute
-
23686
if left.is_a?(Arel::Attributes::Attribute)
-
23668
yield left
-
18
elsif right.is_a?(Arel::Attributes::Attribute)
-
3
yield right
-
end
-
end
-
end
-
-
6
class Between < Binary; include FetchAttribute; end
-
-
3
class GreaterThan < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
3
Arel::Nodes::LessThanOrEqual.new(left, right)
-
end
-
end
-
-
3
class GreaterThanOrEqual < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
6
Arel::Nodes::LessThan.new(left, right)
-
end
-
end
-
-
3
class LessThan < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
3
Arel::Nodes::GreaterThanOrEqual.new(left, right)
-
end
-
end
-
-
3
class LessThanOrEqual < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
3
Arel::Nodes::GreaterThan.new(left, right)
-
end
-
end
-
-
3
class IsDistinctFrom < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
3
Arel::Nodes::IsNotDistinctFrom.new(left, right)
-
end
-
end
-
-
3
class IsNotDistinctFrom < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
3
Arel::Nodes::IsDistinctFrom.new(left, right)
-
end
-
end
-
-
3
class NotEqual < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
3
Arel::Nodes::Equality.new(left, right)
-
end
-
end
-
-
3
class NotIn < Binary
-
3
include FetchAttribute
-
-
3
def invert
-
3
Arel::Nodes::In.new(left, right)
-
end
-
end
-
-
3
class Or < Binary
-
3
def fetch_attribute(&block)
-
102
left.fetch_attribute(&block) && right.fetch_attribute(&block)
-
end
-
end
-
-
%w{
-
As
-
Assignment
-
Join
-
Union
-
UnionAll
-
Intersect
-
Except
-
3
}.each do |name|
-
21
const_set name, Class.new(Binary)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class BindParam < Node
-
3
attr_reader :value
-
-
3
def initialize(value)
-
112245
@value = value
-
112245
super()
-
end
-
-
3
def hash
-
[self.class, self.value].hash
-
end
-
-
3
def eql?(other)
-
275
other.is_a?(BindParam) &&
-
value == other.value
-
end
-
3
alias :== :eql?
-
-
3
def nil?
-
45963
value.nil?
-
end
-
-
3
def value_before_type_cast
-
6381
if value.respond_to?(:value_before_type_cast)
-
6381
value.value_before_type_cast
-
else
-
value
-
end
-
end
-
-
3
def infinite?
-
254
value.respond_to?(:infinite?) && value.infinite?
-
end
-
-
3
def unboundable?
-
66079
value.respond_to?(:unboundable?) && value.unboundable?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Case < Arel::Nodes::NodeExpression
-
3
attr_accessor :case, :conditions, :default
-
-
3
def initialize(expression = nil, default = nil)
-
42
@case = expression
-
42
@conditions = []
-
42
@default = default
-
end
-
-
3
def when(condition, expression = nil)
-
24
@conditions << When.new(Nodes.build_quoted(condition), expression)
-
24
self
-
end
-
-
3
def then(expression)
-
18
@conditions.last.right = Nodes.build_quoted(expression)
-
18
self
-
end
-
-
3
def else(expression)
-
12
@default = Else.new Nodes.build_quoted(expression)
-
12
self
-
end
-
-
3
def initialize_copy(other)
-
3
super
-
3
@case = @case.clone if @case
-
6
@conditions = @conditions.map { |x| x.clone }
-
3
@default = @default.clone if @default
-
end
-
-
3
def hash
-
12
[@case, @conditions, @default].hash
-
end
-
-
3
def eql?(other)
-
6
self.class == other.class &&
-
self.case == other.case &&
-
self.conditions == other.conditions &&
-
self.default == other.default
-
end
-
3
alias :== :eql?
-
end
-
-
3
class When < Binary # :nodoc:
-
end
-
-
3
class Else < Unary # :nodoc:
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Casted < Arel::Nodes::NodeExpression # :nodoc:
-
3
attr_reader :value, :attribute
-
3
alias :value_before_type_cast :value
-
-
3
def initialize(value, attribute)
-
2501
@value = value
-
2501
@attribute = attribute
-
2501
super()
-
end
-
-
252
def nil?; value.nil?; end
-
-
3
def value_for_database
-
579
if attribute.able_to_type_cast?
-
21
attribute.type_cast_for_database(value)
-
else
-
558
value
-
end
-
end
-
-
3
def hash
-
6
[self.class, value, attribute].hash
-
end
-
-
3
def eql?(other)
-
168
self.class == other.class &&
-
self.value == other.value &&
-
self.attribute == other.attribute
-
end
-
3
alias :== :eql?
-
end
-
-
3
class Quoted < Arel::Nodes::Unary # :nodoc:
-
3
alias :value_for_database :value
-
3
alias :value_before_type_cast :value
-
-
93
def nil?; value.nil?; end
-
-
3
def infinite?
-
48
value.respond_to?(:infinite?) && value.infinite?
-
end
-
end
-
-
3
def self.build_quoted(other, attribute = nil)
-
69604
case other
-
when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral
-
64164
other
-
else
-
5440
case attribute
-
when Arel::Attributes::Attribute
-
2426
Casted.new other, attribute
-
else
-
3014
Quoted.new other
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Comment < Arel::Nodes::Node
-
3
attr_reader :values
-
-
3
def initialize(values)
-
123
super()
-
123
@values = values
-
end
-
-
3
def initialize_copy(other)
-
super
-
@values = @values.clone
-
end
-
-
3
def hash
-
30
[@values].hash
-
end
-
-
3
def eql?(other)
-
6
self.class == other.class &&
-
self.values == other.values
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Count < Arel::Nodes::Function
-
3
def initialize(expr, distinct = false, aliaz = nil)
-
4017
super(expr, aliaz)
-
4017
@distinct = distinct
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class DeleteStatement < Arel::Nodes::Node
-
3
attr_accessor :left, :right, :orders, :limit, :offset, :key
-
-
3
alias :relation :left
-
3
alias :relation= :left=
-
3
alias :wheres :right
-
3
alias :wheres= :right=
-
-
3
def initialize(relation = nil, wheres = [])
-
3381
super()
-
3381
@left = relation
-
3381
@right = wheres
-
3381
@orders = []
-
3381
@limit = nil
-
3381
@offset = nil
-
3381
@key = nil
-
end
-
-
3
def initialize_copy(other)
-
99
super
-
99
@left = @left.clone if @left
-
99
@right = @right.clone if @right
-
end
-
-
3
def hash
-
12
[self.class, @left, @right, @orders, @limit, @offset, @key].hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class &&
-
self.left == other.left &&
-
self.right == other.right &&
-
self.orders == other.orders &&
-
self.limit == other.limit &&
-
self.offset == other.offset &&
-
self.key == other.key
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Descending < Ordering
-
3
def reverse
-
21
Ascending.new(expr)
-
end
-
-
3
def direction
-
3
:desc
-
end
-
-
3
def ascending?
-
3
false
-
end
-
-
3
def descending?
-
9
true
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Equality < Arel::Nodes::Binary
-
3
include FetchAttribute
-
-
8351
def equality?; true; end
-
-
3
def invert
-
199
Arel::Nodes::NotEqual.new(left, right)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Extract < Arel::Nodes::Unary
-
3
attr_accessor :field
-
-
3
def initialize(expr, field)
-
21
super(expr)
-
21
@field = field
-
end
-
-
3
def hash
-
12
super ^ @field.hash
-
end
-
-
3
def eql?(other)
-
6
super &&
-
self.field == other.field
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class False < Arel::Nodes::NodeExpression
-
3
def hash
-
9
self.class.hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class FullOuterJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Function < Arel::Nodes::NodeExpression
-
3
include Arel::WindowPredications
-
3
attr_accessor :expressions, :alias, :distinct
-
-
3
def initialize(expr, aliaz = nil)
-
5762
super()
-
5762
@expressions = expr
-
5762
@alias = aliaz && SqlLiteral.new(aliaz)
-
5762
@distinct = false
-
end
-
-
3
def as(aliaz)
-
274
self.alias = SqlLiteral.new(aliaz)
-
274
self
-
end
-
-
3
def hash
-
42
[@expressions, @alias, @distinct].hash
-
end
-
-
3
def eql?(other)
-
9
self.class == other.class &&
-
self.expressions == other.expressions &&
-
self.alias == other.alias &&
-
self.distinct == other.distinct
-
end
-
3
alias :== :eql?
-
end
-
-
%w{
-
Sum
-
Exists
-
Max
-
Min
-
Avg
-
3
}.each do |name|
-
15
const_set(name, Class.new(Function))
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Grouping < Unary
-
3
def fetch_attribute(&block)
-
114
expr.fetch_attribute(&block)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class HomogeneousIn < Node
-
3
attr_reader :attribute, :values, :type
-
-
3
def initialize(values, attribute, type)
-
7823
@values = values
-
7823
@attribute = attribute
-
7823
@type = type
-
end
-
-
3
def hash
-
ivars.hash
-
end
-
-
3
def eql?(other)
-
4189
super || (self.class == other.class && self.ivars == other.ivars)
-
end
-
3
alias :== :eql?
-
-
3
def equality?
-
3903
type == :in
-
end
-
-
3
def invert
-
21
Arel::Nodes::HomogeneousIn.new(values, attribute, type == :in ? :notin : :in)
-
end
-
-
3
def left
-
686
attribute
-
end
-
-
3
def right
-
343
attribute.quoted_array(values)
-
end
-
-
3
def table_name
-
3975
attribute.relation.table_alias || attribute.relation.name
-
end
-
-
3
def column_name
-
3975
attribute.name
-
end
-
-
3
def casted_values
-
3975
type = attribute.type_caster
-
-
3975
casted_values = values.map do |raw_value|
-
1139484
type.serialize(raw_value) if type.serializable?(raw_value)
-
end
-
-
3975
casted_values.compact!
-
3975
casted_values
-
end
-
-
3
def fetch_attribute(&block)
-
6450
if attribute
-
6450
yield attribute
-
else
-
expr.fetch_attribute(&block)
-
end
-
end
-
-
3
protected
-
3
def ivars
-
210
[@attribute, @values, @type]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class In < Arel::Nodes::Binary
-
3
include FetchAttribute
-
-
3
def equality?; true; end
-
-
3
def invert
-
6
Arel::Nodes::NotIn.new(left, right)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class InfixOperation < Binary
-
3
include Arel::Expressions
-
3
include Arel::Predications
-
3
include Arel::OrderPredications
-
3
include Arel::AliasPredication
-
3
include Arel::Math
-
-
3
attr_reader :operator
-
-
3
def initialize(operator, left, right)
-
1274
super(left, right)
-
1274
@operator = operator
-
end
-
end
-
-
3
class Multiplication < InfixOperation
-
3
def initialize(left, right)
-
18
super(:*, left, right)
-
end
-
end
-
-
3
class Division < InfixOperation
-
3
def initialize(left, right)
-
18
super(:/, left, right)
-
end
-
end
-
-
3
class Addition < InfixOperation
-
3
def initialize(left, right)
-
844
super(:+, left, right)
-
end
-
end
-
-
3
class Subtraction < InfixOperation
-
3
def initialize(left, right)
-
241
super(:-, left, right)
-
end
-
end
-
-
3
class Concat < InfixOperation
-
3
def initialize(left, right)
-
9
super(:"||", left, right)
-
end
-
end
-
-
3
class Contains < InfixOperation
-
3
def initialize(left, right)
-
12
super(:"@>", left, right)
-
end
-
end
-
-
3
class Overlaps < InfixOperation
-
3
def initialize(left, right)
-
12
super(:"&&", left, right)
-
end
-
end
-
-
3
class BitwiseAnd < InfixOperation
-
3
def initialize(left, right)
-
18
super(:&, left, right)
-
end
-
end
-
-
3
class BitwiseOr < InfixOperation
-
3
def initialize(left, right)
-
18
super(:|, left, right)
-
end
-
end
-
-
3
class BitwiseXor < InfixOperation
-
3
def initialize(left, right)
-
18
super(:^, left, right)
-
end
-
end
-
-
3
class BitwiseShiftLeft < InfixOperation
-
3
def initialize(left, right)
-
18
super(:<<, left, right)
-
end
-
end
-
-
3
class BitwiseShiftRight < InfixOperation
-
3
def initialize(left, right)
-
18
super(:>>, left, right)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class InnerJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class InsertStatement < Arel::Nodes::Node
-
3
attr_accessor :relation, :columns, :values, :select
-
-
3
def initialize
-
165968
super()
-
165968
@relation = nil
-
165968
@columns = []
-
165968
@values = nil
-
165968
@select = nil
-
end
-
-
3
def initialize_copy(other)
-
3
super
-
3
@columns = @columns.clone
-
3
@values = @values.clone if @values
-
3
@select = @select.clone if @select
-
end
-
-
3
def hash
-
12
[@relation, @columns, @values, @select].hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class &&
-
self.relation == other.relation &&
-
self.columns == other.columns &&
-
self.select == other.select &&
-
self.values == other.values
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
###
-
# Class that represents a join source
-
#
-
# https://www.sqlite.org/syntaxdiagrams.html#join-source
-
-
3
class JoinSource < Arel::Nodes::Binary
-
3
def initialize(single_source, joinop = [])
-
45309
super
-
end
-
-
3
def empty?
-
34921
!left && right.empty?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Matches < Binary
-
3
attr_reader :escape
-
3
attr_accessor :case_sensitive
-
-
3
def initialize(left, right, escape = nil, case_sensitive = false)
-
129
super(left, right)
-
129
@escape = escape && Nodes.build_quoted(escape)
-
129
@case_sensitive = case_sensitive
-
end
-
end
-
-
3
class DoesNotMatch < Matches; end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class NamedFunction < Arel::Nodes::Function
-
3
attr_accessor :name
-
-
3
def initialize(name, expr, aliaz = nil)
-
1206
super(expr, aliaz)
-
1206
@name = name
-
end
-
-
3
def hash
-
18
super ^ @name.hash
-
end
-
-
3
def eql?(other)
-
3
super && self.name == other.name
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
###
-
# Abstract base class for all AST nodes
-
3
class Node
-
3
include Arel::FactoryMethods
-
-
###
-
# Factory method to create a Nodes::Not node that has the recipient of
-
# the caller as a child.
-
3
def not
-
3
Nodes::Not.new self
-
end
-
-
###
-
# Factory method to create a Nodes::Grouping node that has an Nodes::Or
-
# node as a child.
-
3
def or(right)
-
84
Nodes::Grouping.new Nodes::Or.new(self, right)
-
end
-
-
###
-
# Factory method to create an Nodes::And node.
-
3
def and(right)
-
511
Nodes::And.new [self, right]
-
end
-
-
3
def invert
-
3
Arel::Nodes::Not.new(self)
-
end
-
-
# FIXME: this method should go away. I don't like people calling
-
# to_sql on non-head nodes. This forces us to walk the AST until we
-
# can find a node that has a "relation" member.
-
#
-
# Maybe we should just use `Table.engine`? :'(
-
3
def to_sql(engine = Table.engine)
-
469
collector = Arel::Collectors::SQLString.new
-
469
collector = engine.connection.visitor.accept self, collector
-
469
collector.value
-
end
-
-
3
def fetch_attribute
-
end
-
-
75
def equality?; false; end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class NodeExpression < Arel::Nodes::Node
-
3
include Arel::Expressions
-
3
include Arel::Predications
-
3
include Arel::AliasPredication
-
3
include Arel::OrderPredications
-
3
include Arel::Math
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Ordering < Unary
-
3
def nulls_first
-
3
NullsFirst.new(self)
-
end
-
-
3
def nulls_last
-
3
NullsLast.new(self)
-
end
-
end
-
-
3
class NullsFirst < Ordering; end
-
3
class NullsLast < Ordering; end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class OuterJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Over < Binary
-
3
include Arel::AliasPredication
-
-
3
def initialize(left, right = nil)
-
27
super(left, right)
-
end
-
-
3
def operator; "OVER" end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Regexp < Binary
-
3
attr_accessor :case_sensitive
-
-
3
def initialize(left, right, case_sensitive = true)
-
36
super(left, right)
-
36
@case_sensitive = case_sensitive
-
end
-
end
-
-
3
class NotRegexp < Regexp; end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class RightOuterJoin < Arel::Nodes::Join
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class SelectCore < Arel::Nodes::Node
-
3
attr_accessor :projections, :wheres, :groups, :windows, :comment
-
3
attr_accessor :havings, :source, :set_quantifier, :optimizer_hints
-
-
3
def initialize
-
45303
super()
-
45303
@source = JoinSource.new nil
-
-
# https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier
-
45303
@set_quantifier = nil
-
45303
@optimizer_hints = nil
-
45303
@projections = []
-
45303
@wheres = []
-
45303
@groups = []
-
45303
@havings = []
-
45303
@windows = []
-
45303
@comment = nil
-
end
-
-
3
def from
-
45
@source.left
-
end
-
-
3
def from=(value)
-
207
@source.left = value
-
end
-
-
3
alias :froms= :from=
-
3
alias :froms :from
-
-
3
def initialize_copy(other)
-
12
super
-
12
@source = @source.clone if @source
-
12
@projections = @projections.clone
-
12
@wheres = @wheres.clone
-
12
@groups = @groups.clone
-
12
@havings = @havings.clone
-
12
@windows = @windows.clone
-
end
-
-
3
def hash
-
[
-
@source, @set_quantifier, @projections, @optimizer_hints,
-
@wheres, @groups, @havings, @windows, @comment
-
18
].hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class &&
-
self.source == other.source &&
-
self.set_quantifier == other.set_quantifier &&
-
self.optimizer_hints == other.optimizer_hints &&
-
self.projections == other.projections &&
-
self.wheres == other.wheres &&
-
self.groups == other.groups &&
-
self.havings == other.havings &&
-
self.windows == other.windows &&
-
self.comment == other.comment
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class SelectStatement < Arel::Nodes::NodeExpression
-
3
attr_reader :cores
-
3
attr_accessor :limit, :orders, :lock, :offset, :with
-
-
3
def initialize(cores = [SelectCore.new])
-
45291
super()
-
45291
@cores = cores
-
45291
@orders = []
-
45291
@limit = nil
-
45291
@lock = nil
-
45291
@offset = nil
-
45291
@with = nil
-
end
-
-
3
def initialize_copy(other)
-
12
super
-
30
@cores = @cores.map { |x| x.clone }
-
12
@orders = @orders.map { |x| x.clone }
-
end
-
-
3
def hash
-
12
[@cores, @orders, @limit, @lock, @offset, @with].hash
-
end
-
-
3
def eql?(other)
-
12
self.class == other.class &&
-
self.cores == other.cores &&
-
self.orders == other.orders &&
-
self.limit == other.limit &&
-
self.lock == other.lock &&
-
self.offset == other.offset &&
-
self.with == other.with
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class SqlLiteral < String
-
3
include Arel::Expressions
-
3
include Arel::Predications
-
3
include Arel::AliasPredication
-
3
include Arel::OrderPredications
-
-
3
def encode_with(coder)
-
3
coder.scalar = self.to_s
-
end
-
-
3
def fetch_attribute
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class StringJoin < Arel::Nodes::Join
-
3
def initialize(left, right = nil)
-
105
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class TableAlias < Arel::Nodes::Binary
-
3
alias :name :right
-
3
alias :relation :left
-
3
alias :table_alias :name
-
-
3
def [](name)
-
2508
relation.is_a?(Table) ? relation[name, self] : Attribute.new(self, name)
-
end
-
-
3
def table_name
-
6
relation.respond_to?(:name) ? relation.name : name
-
end
-
-
3
def type_cast_for_database(attr_name, value)
-
relation.type_cast_for_database(attr_name, value)
-
end
-
-
3
def type_for_attribute(name)
-
171
relation.type_for_attribute(name)
-
end
-
-
3
def able_to_type_cast?
-
6
relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast?
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Distinct < Arel::Nodes::NodeExpression
-
3
def hash
-
9
self.class.hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class True < Arel::Nodes::NodeExpression
-
3
def hash
-
9
self.class.hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Unary < Arel::Nodes::NodeExpression
-
3
attr_accessor :expr
-
3
alias :value :expr
-
-
3
def initialize(expr)
-
217719
super()
-
217719
@expr = expr
-
end
-
-
3
def hash
-
144
@expr.hash
-
end
-
-
3
def eql?(other)
-
18577
self.class == other.class &&
-
self.expr == other.expr
-
end
-
3
alias :== :eql?
-
end
-
-
%w{
-
Bin
-
Cube
-
DistinctOn
-
Group
-
GroupingElement
-
GroupingSet
-
Lateral
-
Limit
-
Lock
-
Not
-
Offset
-
On
-
OptimizerHints
-
RollUp
-
3
}.each do |name|
-
42
const_set(name, Class.new(Unary))
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class UnaryOperation < Unary
-
3
attr_reader :operator
-
-
3
def initialize(operator, operand)
-
27
super(operand)
-
27
@operator = operator
-
end
-
end
-
-
3
class BitwiseNot < UnaryOperation
-
3
def initialize(operand)
-
3
super(:~, operand)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class UnqualifiedColumn < Arel::Nodes::Unary
-
3
alias :attribute :expr
-
3
alias :attribute= :expr=
-
-
3
def relation
-
@expr.relation
-
end
-
-
3
def column
-
@expr.column
-
end
-
-
3
def name
-
7183
@expr.name
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class UpdateStatement < Arel::Nodes::Node
-
3
attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key
-
-
3
def initialize
-
4204
@relation = nil
-
4204
@wheres = []
-
4204
@values = []
-
4204
@orders = []
-
4204
@limit = nil
-
4204
@offset = nil
-
4204
@key = nil
-
end
-
-
3
def initialize_copy(other)
-
93
super
-
93
@wheres = @wheres.clone
-
93
@values = @values.clone
-
end
-
-
3
def hash
-
12
[@relation, @wheres, @values, @orders, @limit, @offset, @key].hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class &&
-
self.relation == other.relation &&
-
self.wheres == other.wheres &&
-
self.values == other.values &&
-
self.orders == other.orders &&
-
self.limit == other.limit &&
-
self.offset == other.offset &&
-
self.key == other.key
-
end
-
3
alias :== :eql?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class ValuesList < Unary
-
3
alias :rows :expr
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class Window < Arel::Nodes::Node
-
3
attr_accessor :orders, :framing, :partitions
-
-
3
def initialize
-
81
@orders = []
-
81
@partitions = []
-
81
@framing = nil
-
end
-
-
3
def order(*expr)
-
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
-
12
@orders.concat expr.map { |x|
-
15
String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
-
}
-
12
self
-
end
-
-
3
def partition(*expr)
-
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
-
9
@partitions.concat expr.map { |x|
-
12
String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
-
}
-
9
self
-
end
-
-
3
def frame(expr)
-
66
@framing = expr
-
end
-
-
3
def rows(expr = nil)
-
18
if @framing
-
Rows.new(expr)
-
else
-
18
frame(Rows.new(expr))
-
end
-
end
-
-
3
def range(expr = nil)
-
18
if @framing
-
Range.new(expr)
-
else
-
18
frame(Range.new(expr))
-
end
-
end
-
-
3
def initialize_copy(other)
-
super
-
@orders = @orders.map { |x| x.clone }
-
end
-
-
3
def hash
-
24
[@orders, @framing].hash
-
end
-
-
3
def eql?(other)
-
6
self.class == other.class &&
-
self.orders == other.orders &&
-
self.framing == other.framing &&
-
self.partitions == other.partitions
-
end
-
3
alias :== :eql?
-
end
-
-
3
class NamedWindow < Window
-
3
attr_accessor :name
-
-
3
def initialize(name)
-
66
super()
-
66
@name = name
-
end
-
-
3
def initialize_copy(other)
-
super
-
@name = other.name.clone
-
end
-
-
3
def hash
-
12
super ^ @name.hash
-
end
-
-
3
def eql?(other)
-
3
super && self.name == other.name
-
end
-
3
alias :== :eql?
-
end
-
-
3
class Rows < Unary
-
3
def initialize(expr = nil)
-
18
super(expr)
-
end
-
end
-
-
3
class Range < Unary
-
3
def initialize(expr = nil)
-
18
super(expr)
-
end
-
end
-
-
3
class CurrentRow < Node
-
3
def hash
-
9
self.class.hash
-
end
-
-
3
def eql?(other)
-
3
self.class == other.class
-
end
-
3
alias :== :eql?
-
end
-
-
3
class Preceding < Unary
-
3
def initialize(expr = nil)
-
18
super(expr)
-
end
-
end
-
-
3
class Following < Unary
-
3
def initialize(expr = nil)
-
12
super(expr)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Nodes
-
3
class With < Arel::Nodes::Unary
-
3
alias children expr
-
end
-
-
3
class WithRecursive < With; end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module OrderPredications
-
3
def asc
-
4799
Nodes::Ascending.new self
-
end
-
-
3
def desc
-
187
Nodes::Descending.new self
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Predications
-
3
def not_eq(other)
-
45
Nodes::NotEqual.new self, quoted_node(other)
-
end
-
-
3
def not_eq_any(others)
-
6
grouping_any :not_eq, others
-
end
-
-
3
def not_eq_all(others)
-
6
grouping_all :not_eq, others
-
end
-
-
3
def eq(other)
-
59717
Nodes::Equality.new self, quoted_node(other)
-
end
-
-
3
def is_not_distinct_from(other)
-
30
Nodes::IsNotDistinctFrom.new self, quoted_node(other)
-
end
-
-
3
def is_distinct_from(other)
-
27
Nodes::IsDistinctFrom.new self, quoted_node(other)
-
end
-
-
3
def eq_any(others)
-
12
grouping_any :eq, others
-
end
-
-
3
def eq_all(others)
-
18
grouping_all :eq, quoted_array(others)
-
end
-
-
3
def between(other)
-
190
if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
-
9
self.in([])
-
181
elsif open_ended?(other.begin)
-
49
if open_ended?(other.end)
-
19
not_in([])
-
30
elsif other.exclude_end?
-
12
lt(other.end)
-
else
-
18
lteq(other.end)
-
end
-
132
elsif open_ended?(other.end)
-
24
gteq(other.begin)
-
108
elsif other.exclude_end?
-
15
gteq(other.begin).and(lt(other.end))
-
else
-
93
left = quoted_node(other.begin)
-
93
right = quoted_node(other.end)
-
93
Nodes::Between.new(self, left.and(right))
-
end
-
end
-
-
3
def in(other)
-
386
case other
-
when Arel::SelectManager
-
123
Arel::Nodes::In.new(self, other.ast)
-
when Enumerable
-
245
Nodes::In.new self, quoted_array(other)
-
else
-
18
Nodes::In.new self, quoted_node(other)
-
end
-
end
-
-
3
def in_any(others)
-
6
grouping_any :in, others
-
end
-
-
3
def in_all(others)
-
6
grouping_all :in, others
-
end
-
-
3
def not_between(other)
-
48
if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1
-
not_in([])
-
48
elsif open_ended?(other.begin)
-
27
if open_ended?(other.end)
-
9
self.in([])
-
18
elsif other.exclude_end?
-
9
gteq(other.end)
-
else
-
9
gt(other.end)
-
end
-
21
elsif open_ended?(other.end)
-
9
lt(other.begin)
-
else
-
12
left = lt(other.begin)
-
12
right = if other.exclude_end?
-
6
gteq(other.end)
-
else
-
6
gt(other.end)
-
end
-
12
left.or(right)
-
end
-
end
-
-
3
def not_in(other)
-
70
case other
-
when Arel::SelectManager
-
6
Arel::Nodes::NotIn.new(self, other.ast)
-
when Enumerable
-
49
Nodes::NotIn.new self, quoted_array(other)
-
else
-
15
Nodes::NotIn.new self, quoted_node(other)
-
end
-
end
-
-
3
def not_in_any(others)
-
6
grouping_any :not_in, others
-
end
-
-
3
def not_in_all(others)
-
6
grouping_all :not_in, others
-
end
-
-
3
def matches(other, escape = nil, case_sensitive = false)
-
60
Nodes::Matches.new self, quoted_node(other), escape, case_sensitive
-
end
-
-
3
def matches_regexp(other, case_sensitive = true)
-
15
Nodes::Regexp.new self, quoted_node(other), case_sensitive
-
end
-
-
3
def matches_any(others, escape = nil, case_sensitive = false)
-
6
grouping_any :matches, others, escape, case_sensitive
-
end
-
-
3
def matches_all(others, escape = nil, case_sensitive = false)
-
6
grouping_all :matches, others, escape, case_sensitive
-
end
-
-
3
def does_not_match(other, escape = nil, case_sensitive = false)
-
63
Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive
-
end
-
-
3
def does_not_match_regexp(other, case_sensitive = true)
-
15
Nodes::NotRegexp.new self, quoted_node(other), case_sensitive
-
end
-
-
3
def does_not_match_any(others, escape = nil)
-
6
grouping_any :does_not_match, others, escape
-
end
-
-
3
def does_not_match_all(others, escape = nil)
-
12
grouping_all :does_not_match, others, escape
-
end
-
-
3
def gteq(right)
-
129
Nodes::GreaterThanOrEqual.new self, quoted_node(right)
-
end
-
-
3
def gteq_any(others)
-
6
grouping_any :gteq, others
-
end
-
-
3
def gteq_all(others)
-
6
grouping_all :gteq, others
-
end
-
-
3
def gt(right)
-
864
Nodes::GreaterThan.new self, quoted_node(right)
-
end
-
-
3
def gt_any(others)
-
6
grouping_any :gt, others
-
end
-
-
3
def gt_all(others)
-
6
grouping_all :gt, others
-
end
-
-
3
def lt(right)
-
165
Nodes::LessThan.new self, quoted_node(right)
-
end
-
-
3
def lt_any(others)
-
6
grouping_any :lt, others
-
end
-
-
3
def lt_all(others)
-
6
grouping_all :lt, others
-
end
-
-
3
def lteq(right)
-
75
Nodes::LessThanOrEqual.new self, quoted_node(right)
-
end
-
-
3
def lteq_any(others)
-
6
grouping_any :lteq, others
-
end
-
-
3
def lteq_all(others)
-
6
grouping_all :lteq, others
-
end
-
-
3
def when(right)
-
3
Nodes::Case.new(self).when quoted_node(right)
-
end
-
-
3
def concat(other)
-
9
Nodes::Concat.new self, other
-
end
-
-
3
def contains(other)
-
9
Arel::Nodes::Contains.new(self, other)
-
end
-
-
3
def overlaps(other)
-
9
Arel::Nodes::Overlaps.new(self, other)
-
end
-
-
3
def quoted_array(others)
-
2190
others.map { |v| quoted_node(v) }
-
end
-
-
3
private
-
3
def grouping_any(method_id, others, *extras)
-
198
nodes = others.map { |expr| send(method_id, expr, *extras) }
-
66
Nodes::Grouping.new nodes.inject { |memo, node|
-
66
Nodes::Or.new(memo, node)
-
}
-
end
-
-
3
def grouping_all(method_id, others, *extras)
-
234
nodes = others.map { |expr| send(method_id, expr, *extras) }
-
78
Nodes::Grouping.new Nodes::And.new(nodes)
-
end
-
-
3
def quoted_node(other)
-
62962
Nodes.build_quoted(other, self)
-
end
-
-
3
def infinity?(value)
-
458
value.respond_to?(:infinite?) && value.infinite?
-
end
-
-
3
def unboundable?(value)
-
806
value.respond_to?(:unboundable?) && value.unboundable?
-
end
-
-
3
def open_ended?(value)
-
458
value.nil? || infinity?(value) || unboundable?(value)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
class SelectManager < Arel::TreeManager
-
3
include Arel::Crud
-
-
3
STRING_OR_SYMBOL_CLASS = [Symbol, String]
-
-
3
def initialize(table = nil)
-
45075
super()
-
45075
@ast = Nodes::SelectStatement.new
-
45075
@ctx = @ast.cores.last
-
45075
from table
-
end
-
-
3
def initialize_copy(other)
-
9
super
-
9
@ctx = @ast.cores.last
-
end
-
-
3
def limit
-
3454
@ast.limit && @ast.limit.expr
-
end
-
3
alias :taken :limit
-
-
3
def constraints
-
6590
@ctx.wheres
-
end
-
-
3
def offset
-
3454
@ast.offset && @ast.offset.expr
-
end
-
-
3
def skip(amount)
-
432
if amount
-
429
@ast.offset = Nodes::Offset.new(amount)
-
else
-
3
@ast.offset = nil
-
end
-
432
self
-
end
-
3
alias :offset= :skip
-
-
###
-
# Produces an Arel::Nodes::Exists node
-
3
def exists
-
6
Arel::Nodes::Exists.new @ast
-
end
-
-
3
def as(other)
-
354
create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
-
end
-
-
3
def lock(locking = Arel.sql("FOR UPDATE"))
-
35
case locking
-
when true
-
31
locking = Arel.sql("FOR UPDATE")
-
when Arel::Nodes::SqlLiteral
-
when String
-
1
locking = Arel.sql locking
-
end
-
-
35
@ast.lock = Nodes::Lock.new(locking)
-
35
self
-
end
-
-
3
def locked
-
262
@ast.lock
-
end
-
-
3
def on(*exprs)
-
42
@ctx.source.right.last.right = Nodes::On.new(collapse(exprs))
-
42
self
-
end
-
-
3
def group(*columns)
-
382
columns.each do |column|
-
# FIXME: backwards compat
-
409
column = Nodes::SqlLiteral.new(column) if String === column
-
409
column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
-
-
409
@ctx.groups.push Nodes::Group.new column
-
end
-
382
self
-
end
-
-
3
def from(table)
-
45408
table = Nodes::SqlLiteral.new(table) if String === table
-
-
45408
case table
-
when Nodes::Join
-
9
@ctx.source.right << table
-
else
-
45399
@ctx.source.left = table
-
end
-
-
45408
self
-
end
-
-
3
def froms
-
6
@ast.cores.map { |x| x.from }.compact
-
end
-
-
3
def join(relation, klass = Nodes::InnerJoin)
-
51
return self unless relation
-
-
45
case relation
-
when String, Nodes::SqlLiteral
-
3
raise EmptyJoinError if relation.empty?
-
klass = Nodes::StringJoin
-
end
-
-
42
@ctx.source.right << create_join(relation, nil, klass)
-
42
self
-
end
-
-
3
def outer_join(relation)
-
6
join(relation, Nodes::OuterJoin)
-
end
-
-
3
def having(expr)
-
56
@ctx.havings << expr
-
56
self
-
end
-
-
3
def window(name)
-
54
window = Nodes::NamedWindow.new(name)
-
54
@ctx.windows.push window
-
54
window
-
end
-
-
3
def project(*projections)
-
# FIXME: converting these to SQLLiterals is probably not good, but
-
# rails tests require it.
-
41966
@ctx.projections.concat projections.map { |x|
-
72611
STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
-
}
-
41966
self
-
end
-
-
3
def projections
-
3
@ctx.projections
-
end
-
-
3
def projections=(projections)
-
3
@ctx.projections = projections
-
end
-
-
3
def optimizer_hints(*hints)
-
9
unless hints.empty?
-
9
@ctx.optimizer_hints = Arel::Nodes::OptimizerHints.new(hints)
-
end
-
9
self
-
end
-
-
3
def distinct(value = true)
-
41345
if value
-
565
@ctx.set_quantifier = Arel::Nodes::Distinct.new
-
else
-
40780
@ctx.set_quantifier = nil
-
end
-
41345
self
-
end
-
-
3
def distinct_on(value)
-
12
if value
-
6
@ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value)
-
else
-
6
@ctx.set_quantifier = nil
-
end
-
12
self
-
end
-
-
3
def order(*expr)
-
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
-
9078
@ast.orders.concat expr.map { |x|
-
9309
STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
-
}
-
9078
self
-
end
-
-
3
def orders
-
3694
@ast.orders
-
end
-
-
3
def where_sql(engine = Table.engine)
-
114
return if @ctx.wheres.empty?
-
-
111
Nodes::SqlLiteral.new("WHERE #{Nodes::And.new(@ctx.wheres).to_sql(engine)}")
-
end
-
-
3
def union(operation, other = nil)
-
12
if other
-
3
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
-
else
-
9
other = operation
-
9
node_class = Nodes::Union
-
end
-
-
12
node_class.new self.ast, other.ast
-
end
-
-
3
def intersect(other)
-
3
Nodes::Intersect.new ast, other.ast
-
end
-
-
3
def except(other)
-
3
Nodes::Except.new ast, other.ast
-
end
-
3
alias :minus :except
-
-
3
def lateral(table_name = nil)
-
6
base = table_name.nil? ? ast : as(table_name)
-
6
Nodes::Lateral.new(base)
-
end
-
-
3
def with(*subqueries)
-
12
if subqueries.first.is_a? Symbol
-
6
node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}")
-
else
-
6
node_class = Nodes::With
-
end
-
12
@ast.with = node_class.new(subqueries.flatten)
-
-
12
self
-
end
-
-
3
def take(limit)
-
20285
if limit
-
20282
@ast.limit = Nodes::Limit.new(limit)
-
else
-
3
@ast.limit = nil
-
end
-
20285
self
-
end
-
3
alias limit= take
-
-
3
def join_sources
-
10529
@ctx.source.right
-
end
-
-
3
def source
-
45
@ctx.source
-
end
-
-
3
def comment(*values)
-
96
@ctx.comment = Nodes::Comment.new(values)
-
96
self
-
end
-
-
3
private
-
3
def collapse(exprs)
-
42
exprs = exprs.compact
-
42
exprs.map! { |expr|
-
54
if String === expr
-
# FIXME: Don't do this automatically
-
9
Arel.sql(expr)
-
else
-
45
expr
-
end
-
}
-
-
42
if exprs.length == 1
-
33
exprs.first
-
else
-
9
create_and exprs
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
class Table
-
3
include Arel::Crud
-
3
include Arel::FactoryMethods
-
3
include Arel::AliasPredication
-
-
3
@engine = nil
-
6
class << self; attr_accessor :engine; end
-
-
3
attr_accessor :name, :table_alias
-
-
# TableAlias and Table both have a #table_name which is the name of the underlying table
-
3
alias :table_name :name
-
-
3
def initialize(name, as: nil, klass: nil, type_caster: klass&.type_caster)
-
158436
@name = name.to_s
-
158436
@klass = klass
-
158436
@type_caster = type_caster
-
-
# Sometime AR sends an :as parameter to table, to let the table know
-
# that it is an Alias. We may want to override new, and return a
-
# TableAlias node?
-
158436
if as.to_s == @name
-
6
as = nil
-
end
-
158436
@table_alias = as
-
end
-
-
3
def alias(name = "#{self.name}_2")
-
915
Nodes::TableAlias.new(self, name)
-
end
-
-
3
def from
-
3106
SelectManager.new(self)
-
end
-
-
3
def join(relation, klass = Nodes::InnerJoin)
-
18
return from unless relation
-
-
15
case relation
-
when String, Nodes::SqlLiteral
-
3
raise EmptyJoinError if relation.empty?
-
klass = Nodes::StringJoin
-
end
-
-
12
from.join(relation, klass)
-
end
-
-
3
def outer_join(relation)
-
3
join(relation, Nodes::OuterJoin)
-
end
-
-
3
def group(*columns)
-
3
from.group(*columns)
-
end
-
-
3
def order(*expr)
-
6
from.order(*expr)
-
end
-
-
3
def where(condition)
-
2797
from.where condition
-
end
-
-
3
def project(*things)
-
228
from.project(*things)
-
end
-
-
3
def take(amount)
-
3
from.take amount
-
end
-
-
3
def skip(amount)
-
3
from.skip amount
-
end
-
-
3
def having(expr)
-
3
from.having expr
-
end
-
-
3
def [](name, table = self)
-
575647
name = name.to_s if name.is_a?(Symbol)
-
575647
name = @klass.attribute_aliases[name] || name if @klass
-
575647
Attribute.new(table, name)
-
end
-
-
3
def hash
-
# Perf note: aliases and table alias is excluded from the hash
-
# aliases can have a loop back to this table breaking hashes in parent
-
# relations, for the vast majority of cases @name is unique to a query
-
25241
@name.hash
-
end
-
-
3
def eql?(other)
-
13497
self.class == other.class &&
-
self.name == other.name &&
-
self.table_alias == other.table_alias
-
end
-
3
alias :== :eql?
-
-
3
def type_cast_for_database(attr_name, value)
-
21
type_caster.type_cast_for_database(attr_name, value)
-
end
-
-
3
def type_for_attribute(name)
-
157820
type_caster.type_for_attribute(name)
-
end
-
-
3
def able_to_type_cast?
-
585
!type_caster.nil?
-
end
-
-
3
private
-
3
attr_reader :type_caster
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
class TreeManager
-
3
include Arel::FactoryMethods
-
-
3
module StatementMethods
-
3
def take(limit)
-
3463
@ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
-
3463
self
-
end
-
-
3
def offset(offset)
-
3451
@ast.offset = Nodes::Offset.new(Nodes.build_quoted(offset)) if offset
-
3451
self
-
end
-
-
3
def order(*expr)
-
6263
@ast.orders = expr
-
6263
self
-
end
-
-
3
def key=(key)
-
6281
@ast.key = Nodes.build_quoted(key)
-
end
-
-
3
def key
-
3
@ast.key
-
end
-
-
3
def wheres=(exprs)
-
7489
@ast.wheres = exprs
-
end
-
-
3
def where(expr)
-
12
@ast.wheres << expr
-
12
self
-
end
-
end
-
-
3
attr_reader :ast
-
-
3
def initialize
-
218577
@ctx = nil
-
end
-
-
3
def to_dot
-
collector = Arel::Collectors::PlainString.new
-
collector = Visitors::Dot.new.accept @ast, collector
-
collector.value
-
end
-
-
3
def to_sql(engine = Table.engine)
-
528
collector = Arel::Collectors::SQLString.new
-
528
collector = engine.connection.visitor.accept @ast, collector
-
528
collector.value
-
end
-
-
3
def initialize_copy(other)
-
9
super
-
9
@ast = @ast.clone
-
end
-
-
3
def where(expr)
-
35498
if Arel::TreeManager === expr
-
expr = expr.ast
-
end
-
35498
@ctx.wheres << expr
-
35498
self
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
class UpdateManager < Arel::TreeManager
-
3
include TreeManager::StatementMethods
-
-
3
def initialize
-
4186
super
-
4186
@ast = Nodes::UpdateStatement.new
-
4186
@ctx = @ast
-
end
-
-
###
-
# UPDATE +table+
-
3
def table(table)
-
4174
@ast.relation = table
-
4174
self
-
end
-
-
3
def set(values)
-
4162
if String === values
-
78
@ast.values = [values]
-
else
-
4084
@ast.values = values.map { |column, value|
-
6137
Nodes::Assignment.new(
-
Nodes::UnqualifiedColumn.new(column),
-
value
-
)
-
}
-
end
-
4162
self
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "arel/visitors/visitor"
-
3
require "arel/visitors/to_sql"
-
3
require "arel/visitors/sqlite"
-
3
require "arel/visitors/postgresql"
-
3
require "arel/visitors/mysql"
-
3
require "arel/visitors/dot"
-
-
3
module Arel # :nodoc: all
-
3
module Visitors
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Visitors
-
3
class Dot < Arel::Visitors::Visitor
-
3
class Node # :nodoc:
-
3
attr_accessor :name, :id, :fields
-
-
3
def initialize(name, id, fields = [])
-
300
@name = name
-
300
@id = id
-
300
@fields = fields
-
end
-
end
-
-
3
class Edge < Struct.new :name, :from, :to # :nodoc:
-
end
-
-
3
def initialize
-
105
super()
-
105
@nodes = []
-
105
@edges = []
-
105
@node_stack = []
-
105
@edge_stack = []
-
105
@seen = {}
-
end
-
-
3
def accept(object, collector)
-
105
visit object
-
105
collector << to_dot
-
end
-
-
3
private
-
3
def visit_Arel_Nodes_Ordering(o)
-
3
visit_edge o, "expr"
-
end
-
-
3
def visit_Arel_Nodes_TableAlias(o)
-
3
visit_edge o, "name"
-
3
visit_edge o, "relation"
-
end
-
-
3
def visit_Arel_Nodes_Count(o)
-
visit_edge o, "expressions"
-
visit_edge o, "distinct"
-
end
-
-
3
def visit_Arel_Nodes_ValuesList(o)
-
3
visit_edge o, "rows"
-
end
-
-
3
def visit_Arel_Nodes_StringJoin(o)
-
visit_edge o, "left"
-
end
-
-
3
def visit_Arel_Nodes_InnerJoin(o)
-
visit_edge o, "left"
-
visit_edge o, "right"
-
end
-
3
alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_InnerJoin
-
3
alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
-
3
alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_InnerJoin
-
-
3
def visit_Arel_Nodes_DeleteStatement(o)
-
3
visit_edge o, "relation"
-
3
visit_edge o, "wheres"
-
end
-
-
3
def unary(o)
-
21
visit_edge o, "expr"
-
end
-
3
alias :visit_Arel_Nodes_Group :unary
-
3
alias :visit_Arel_Nodes_Cube :unary
-
3
alias :visit_Arel_Nodes_RollUp :unary
-
3
alias :visit_Arel_Nodes_GroupingSet :unary
-
3
alias :visit_Arel_Nodes_GroupingElement :unary
-
3
alias :visit_Arel_Nodes_Grouping :unary
-
3
alias :visit_Arel_Nodes_Having :unary
-
3
alias :visit_Arel_Nodes_Limit :unary
-
3
alias :visit_Arel_Nodes_Not :unary
-
3
alias :visit_Arel_Nodes_Offset :unary
-
3
alias :visit_Arel_Nodes_On :unary
-
3
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
-
3
alias :visit_Arel_Nodes_OptimizerHints :unary
-
3
alias :visit_Arel_Nodes_Preceding :unary
-
3
alias :visit_Arel_Nodes_Following :unary
-
3
alias :visit_Arel_Nodes_Rows :unary
-
3
alias :visit_Arel_Nodes_Range :unary
-
-
3
def window(o)
-
visit_edge o, "partitions"
-
visit_edge o, "orders"
-
visit_edge o, "framing"
-
end
-
3
alias :visit_Arel_Nodes_Window :window
-
-
3
def named_window(o)
-
visit_edge o, "partitions"
-
visit_edge o, "orders"
-
visit_edge o, "framing"
-
visit_edge o, "name"
-
end
-
3
alias :visit_Arel_Nodes_NamedWindow :named_window
-
-
3
def function(o)
-
15
visit_edge o, "expressions"
-
15
visit_edge o, "distinct"
-
15
visit_edge o, "alias"
-
end
-
3
alias :visit_Arel_Nodes_Exists :function
-
3
alias :visit_Arel_Nodes_Min :function
-
3
alias :visit_Arel_Nodes_Max :function
-
3
alias :visit_Arel_Nodes_Avg :function
-
3
alias :visit_Arel_Nodes_Sum :function
-
-
3
def extract(o)
-
visit_edge o, "expressions"
-
visit_edge o, "alias"
-
end
-
3
alias :visit_Arel_Nodes_Extract :extract
-
-
3
def visit_Arel_Nodes_NamedFunction(o)
-
3
visit_edge o, "name"
-
3
visit_edge o, "expressions"
-
3
visit_edge o, "distinct"
-
3
visit_edge o, "alias"
-
end
-
-
3
def visit_Arel_Nodes_InsertStatement(o)
-
visit_edge o, "relation"
-
visit_edge o, "columns"
-
visit_edge o, "values"
-
end
-
-
3
def visit_Arel_Nodes_SelectCore(o)
-
visit_edge o, "source"
-
visit_edge o, "projections"
-
visit_edge o, "wheres"
-
visit_edge o, "windows"
-
end
-
-
3
def visit_Arel_Nodes_SelectStatement(o)
-
visit_edge o, "cores"
-
visit_edge o, "limit"
-
visit_edge o, "orders"
-
visit_edge o, "offset"
-
end
-
-
3
def visit_Arel_Nodes_UpdateStatement(o)
-
visit_edge o, "relation"
-
visit_edge o, "wheres"
-
visit_edge o, "values"
-
end
-
-
3
def visit_Arel_Table(o)
-
visit_edge o, "name"
-
end
-
-
3
def visit_Arel_Nodes_Casted(o)
-
3
visit_edge o, "value"
-
3
visit_edge o, "attribute"
-
end
-
-
3
def visit_Arel_Nodes_HomogeneousIn(o)
-
visit_edge o, "values"
-
visit_edge o, "type"
-
visit_edge o, "attribute"
-
end
-
-
3
def visit_Arel_Attribute(o)
-
visit_edge o, "relation"
-
visit_edge o, "name"
-
end
-
3
alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
-
3
alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
-
3
alias :visit_Arel_Attributes_String :visit_Arel_Attribute
-
3
alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
-
3
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
-
3
alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
-
-
3
def nary(o)
-
o.children.each_with_index do |x, i|
-
edge(i) { visit x }
-
end
-
end
-
3
alias :visit_Arel_Nodes_And :nary
-
-
3
def binary(o)
-
45
visit_edge o, "left"
-
45
visit_edge o, "right"
-
end
-
3
alias :visit_Arel_Nodes_As :binary
-
3
alias :visit_Arel_Nodes_Assignment :binary
-
3
alias :visit_Arel_Nodes_Between :binary
-
3
alias :visit_Arel_Nodes_Concat :binary
-
3
alias :visit_Arel_Nodes_DoesNotMatch :binary
-
3
alias :visit_Arel_Nodes_Equality :binary
-
3
alias :visit_Arel_Nodes_GreaterThan :binary
-
3
alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
-
3
alias :visit_Arel_Nodes_In :binary
-
3
alias :visit_Arel_Nodes_JoinSource :binary
-
3
alias :visit_Arel_Nodes_LessThan :binary
-
3
alias :visit_Arel_Nodes_LessThanOrEqual :binary
-
3
alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
-
3
alias :visit_Arel_Nodes_IsDistinctFrom :binary
-
3
alias :visit_Arel_Nodes_Matches :binary
-
3
alias :visit_Arel_Nodes_NotEqual :binary
-
3
alias :visit_Arel_Nodes_NotIn :binary
-
3
alias :visit_Arel_Nodes_Or :binary
-
3
alias :visit_Arel_Nodes_Over :binary
-
-
3
def visit_String(o)
-
195
@node_stack.last.fields << o
-
end
-
3
alias :visit_Time :visit_String
-
3
alias :visit_Date :visit_String
-
3
alias :visit_DateTime :visit_String
-
3
alias :visit_NilClass :visit_String
-
3
alias :visit_TrueClass :visit_String
-
3
alias :visit_FalseClass :visit_String
-
3
alias :visit_Integer :visit_String
-
3
alias :visit_BigDecimal :visit_String
-
3
alias :visit_Float :visit_String
-
3
alias :visit_Symbol :visit_String
-
3
alias :visit_Arel_Nodes_SqlLiteral :visit_String
-
-
3
def visit_Arel_Nodes_BindParam(o)
-
6
edge("value") { visit o.value }
-
end
-
-
3
def visit_ActiveModel_Attribute(o)
-
6
edge("value_before_type_cast") { visit o.value_before_type_cast }
-
end
-
-
3
def visit_Hash(o)
-
o.each_with_index do |pair, i|
-
edge("pair_#{i}") { visit pair }
-
end
-
end
-
-
3
def visit_Array(o)
-
o.each_with_index do |x, i|
-
edge(i) { visit x }
-
end
-
end
-
3
alias :visit_Set :visit_Array
-
-
3
def visit_Arel_Nodes_Comment(o)
-
visit_edge(o, "values")
-
end
-
-
3
def visit_edge(o, method)
-
384
edge(method) { visit o.send(method) }
-
end
-
-
3
def visit(o)
-
303
if node = @seen[o.object_id]
-
3
@edge_stack.last.to = node
-
3
return
-
end
-
-
300
node = Node.new(o.class.name, o.object_id)
-
300
@seen[node.id] = node
-
300
@nodes << node
-
300
with_node node do
-
300
super
-
end
-
end
-
-
3
def edge(name)
-
198
edge = Edge.new(name, @node_stack.last)
-
198
@edge_stack.push edge
-
198
@edges << edge
-
198
yield
-
198
@edge_stack.pop
-
end
-
-
3
def with_node(node)
-
300
if edge = @edge_stack.last
-
195
edge.to = node
-
end
-
-
300
@node_stack.push node
-
300
yield
-
300
@node_stack.pop
-
end
-
-
3
def quote(string)
-
195
string.to_s.gsub('"', '\"')
-
end
-
-
3
def to_dot
-
"digraph \"Arel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
-
@nodes.map { |node|
-
300
label = "<f0>#{node.name}"
-
-
300
node.fields.each_with_index do |field, i|
-
195
label += "|<f#{i + 1}>#{quote field}"
-
end
-
-
300
"#{node.id} [label=\"#{label}\"];"
-
}.join("\n") + "\n" + @edges.map { |edge|
-
198
"#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.name}\"];"
-
105
}.join("\n") + "\n}"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Visitors
-
3
class MySQL < Arel::Visitors::ToSql
-
3
private
-
3
def visit_Arel_Nodes_Bin(o, collector)
-
3
collector << "BINARY "
-
3
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
-
visit o.expr, collector
-
end
-
-
###
-
# :'(
-
# To retrieve all rows from a certain offset up to the end of the result set,
-
# you can use some large number for the second parameter.
-
# https://dev.mysql.com/doc/refman/en/select.html
-
3
def visit_Arel_Nodes_SelectStatement(o, collector)
-
12
if o.offset && !o.limit
-
3
o.limit = Arel::Nodes::Limit.new(18446744073709551615)
-
end
-
12
super
-
end
-
-
3
def visit_Arel_Nodes_SelectCore(o, collector)
-
12
o.froms ||= Arel.sql("DUAL")
-
12
super
-
end
-
-
3
def visit_Arel_Nodes_Concat(o, collector)
-
6
collector << " CONCAT("
-
6
visit o.left, collector
-
6
collector << ", "
-
6
visit o.right, collector
-
6
collector << ") "
-
6
collector
-
end
-
-
3
def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
-
15
collector = visit o.left, collector
-
15
collector << " <=> "
-
15
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_IsDistinctFrom(o, collector)
-
6
collector << "NOT "
-
6
visit_Arel_Nodes_IsNotDistinctFrom o, collector
-
end
-
-
3
def visit_Arel_Nodes_Regexp(o, collector)
-
6
infix_value o, collector, " REGEXP "
-
end
-
-
3
def visit_Arel_Nodes_NotRegexp(o, collector)
-
6
infix_value o, collector, " NOT REGEXP "
-
end
-
-
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
-
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
-
# these, we must use a subquery.
-
3
def prepare_update_statement(o)
-
3
if o.offset || has_join_sources?(o) && has_limit_or_offset_or_orders?(o)
-
super
-
else
-
3
o
-
end
-
end
-
3
alias :prepare_delete_statement :prepare_update_statement
-
-
# MySQL is too stupid to create a temporary table for use subquery, so we have
-
# to give it some prompting in the form of a subsubquery.
-
3
def build_subselect(key, o)
-
subselect = super
-
-
# Materialize subquery by adding distinct
-
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
-
unless has_limit_or_offset_or_orders?(subselect)
-
core = subselect.cores.last
-
core.set_quantifier = Arel::Nodes::Distinct.new
-
end
-
-
Nodes::SelectStatement.new.tap do |stmt|
-
core = stmt.cores.last
-
core.froms = Nodes::Grouping.new(subselect).as("__active_record_temp")
-
core.projections = [Arel.sql(quote_column_name(key.name))]
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Visitors
-
3
class PostgreSQL < Arel::Visitors::ToSql
-
3
private
-
3
def visit_Arel_Nodes_Matches(o, collector)
-
21
op = o.case_sensitive ? " LIKE " : " ILIKE "
-
21
collector = infix_value o, collector, op
-
21
if o.escape
-
3
collector << " ESCAPE "
-
3
visit o.escape, collector
-
else
-
18
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_DoesNotMatch(o, collector)
-
12
op = o.case_sensitive ? " NOT LIKE " : " NOT ILIKE "
-
12
collector = infix_value o, collector, op
-
12
if o.escape
-
3
collector << " ESCAPE "
-
3
visit o.escape, collector
-
else
-
9
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_Regexp(o, collector)
-
9
op = o.case_sensitive ? " ~ " : " ~* "
-
9
infix_value o, collector, op
-
end
-
-
3
def visit_Arel_Nodes_NotRegexp(o, collector)
-
9
op = o.case_sensitive ? " !~ " : " !~* "
-
9
infix_value o, collector, op
-
end
-
-
3
def visit_Arel_Nodes_DistinctOn(o, collector)
-
3
collector << "DISTINCT ON ( "
-
3
visit(o.expr, collector) << " )"
-
end
-
-
3
def visit_Arel_Nodes_GroupingElement(o, collector)
-
27
collector << "( "
-
27
visit(o.expr, collector) << " )"
-
end
-
-
3
def visit_Arel_Nodes_Cube(o, collector)
-
9
collector << "CUBE"
-
9
grouping_array_or_grouping_element o, collector
-
end
-
-
3
def visit_Arel_Nodes_RollUp(o, collector)
-
9
collector << "ROLLUP"
-
9
grouping_array_or_grouping_element o, collector
-
end
-
-
3
def visit_Arel_Nodes_GroupingSet(o, collector)
-
9
collector << "GROUPING SETS"
-
9
grouping_array_or_grouping_element o, collector
-
end
-
-
3
def visit_Arel_Nodes_Lateral(o, collector)
-
6
collector << "LATERAL "
-
6
grouping_parentheses o, collector
-
end
-
-
3
def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
-
9
collector = visit o.left, collector
-
9
collector << " IS NOT DISTINCT FROM "
-
9
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_IsDistinctFrom(o, collector)
-
8
collector = visit o.left, collector
-
8
collector << " IS DISTINCT FROM "
-
8
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_NullsFirst(o, collector)
-
3
visit o.expr, collector
-
3
collector << " NULLS FIRST"
-
end
-
-
3
def visit_Arel_Nodes_NullsLast(o, collector)
-
3
visit o.expr, collector
-
3
collector << " NULLS LAST"
-
end
-
-
422343
BIND_BLOCK = proc { |i| "$#{i}" }
-
3
private_constant :BIND_BLOCK
-
-
45274
def bind_block; BIND_BLOCK; end
-
-
# Used by Lateral visitor to enclose select queries in parentheses
-
3
def grouping_parentheses(o, collector)
-
6
if o.expr.is_a? Nodes::SelectStatement
-
3
collector << "("
-
3
visit o.expr, collector
-
3
collector << ")"
-
else
-
3
visit o.expr, collector
-
end
-
end
-
-
# Utilized by GroupingSet, Cube & RollUp visitors to
-
# handle grouping aggregation semantics
-
3
def grouping_array_or_grouping_element(o, collector)
-
27
if o.expr.is_a? Array
-
18
collector << "( "
-
18
visit o.expr, collector
-
18
collector << " )"
-
else
-
9
visit o.expr, collector
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Visitors
-
3
class SQLite < Arel::Visitors::ToSql
-
3
private
-
# Locks are not supported in SQLite
-
3
def visit_Arel_Nodes_Lock(o, collector)
-
16
collector
-
end
-
-
3
def visit_Arel_Nodes_SelectStatement(o, collector)
-
22420
o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
-
22420
super
-
end
-
-
3
def visit_Arel_Nodes_True(o, collector)
-
3
collector << "1"
-
end
-
-
3
def visit_Arel_Nodes_False(o, collector)
-
3
collector << "0"
-
end
-
-
3
def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
-
9
collector = visit o.left, collector
-
9
collector << " IS "
-
9
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_IsDistinctFrom(o, collector)
-
10
collector = visit o.left, collector
-
10
collector << " IS NOT "
-
10
visit o.right, collector
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Visitors
-
3
class UnsupportedVisitError < StandardError
-
3
def initialize(object)
-
3
super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead."
-
end
-
end
-
-
3
class ToSql < Arel::Visitors::Visitor
-
3
def initialize(connection)
-
3377
super()
-
3377
@connection = connection
-
end
-
-
3
def compile(node, collector = Arel::Collectors::SQLString.new)
-
207825
accept(node, collector).value
-
end
-
-
3
private
-
3
def visit_Arel_Nodes_DeleteStatement(o, collector)
-
3351
o = prepare_delete_statement(o)
-
-
3351
if has_join_sources?(o)
-
collector << "DELETE "
-
visit o.relation.left, collector
-
collector << " FROM "
-
else
-
3351
collector << "DELETE FROM "
-
end
-
3351
collector = visit o.relation, collector
-
-
3351
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
-
3351
collect_nodes_for o.orders, collector, " ORDER BY "
-
3351
maybe_visit o.limit, collector
-
end
-
-
3
def visit_Arel_Nodes_UpdateStatement(o, collector)
-
4168
o = prepare_update_statement(o)
-
-
4168
collector << "UPDATE "
-
4168
collector = visit o.relation, collector
-
4168
collect_nodes_for o.values, collector, " SET "
-
-
4168
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
-
4168
collect_nodes_for o.orders, collector, " ORDER BY "
-
4168
maybe_visit o.limit, collector
-
end
-
-
3
def visit_Arel_Nodes_InsertStatement(o, collector)
-
165935
collector << "INSERT INTO "
-
165935
collector = visit o.relation, collector
-
-
165935
unless o.columns.empty?
-
164626
collector << " ("
-
164626
o.columns.each_with_index do |x, i|
-
416652
collector << ", " unless i == 0
-
416652
collector << quote_column_name(x.name)
-
end
-
164626
collector << ")"
-
end
-
-
165935
if o.values
-
165926
maybe_visit o.values, collector
-
9
elsif o.select
-
3
maybe_visit o.select, collector
-
else
-
6
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_Exists(o, collector)
-
6
collector << "EXISTS ("
-
6
collector = visit(o.expressions, collector) << ")"
-
6
if o.alias
-
3
collector << " AS "
-
3
visit o.alias, collector
-
else
-
3
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_Casted(o, collector)
-
757
collector << quote(o.value_for_database).to_s
-
end
-
3
alias :visit_Arel_Nodes_Quoted :visit_Arel_Nodes_Casted
-
-
3
def visit_Arel_Nodes_True(o, collector)
-
9
collector << "TRUE"
-
end
-
-
3
def visit_Arel_Nodes_False(o, collector)
-
9
collector << "FALSE"
-
end
-
-
3
def visit_Arel_Nodes_ValuesList(o, collector)
-
164784
collector << "VALUES "
-
-
164784
o.rows.each_with_index do |row, i|
-
306528
collector << ", " unless i == 0
-
306528
collector << "("
-
306528
row.each_with_index do |value, k|
-
1042715
collector << ", " unless k == 0
-
1042715
case value
-
when Nodes::SqlLiteral, Nodes::BindParam
-
342584
collector = visit(value, collector)
-
else
-
700131
collector << quote(value).to_s
-
end
-
end
-
306528
collector << ")"
-
end
-
164784
collector
-
end
-
-
3
def visit_Arel_Nodes_SelectStatement(o, collector)
-
34912
if o.with
-
12
collector = visit o.with, collector
-
12
collector << " "
-
end
-
-
34912
collector = o.cores.inject(collector) { |c, x|
-
34912
visit_Arel_Nodes_SelectCore(x, c)
-
}
-
-
34912
unless o.orders.empty?
-
8717
collector << " ORDER BY "
-
8717
o.orders.each_with_index do |x, i|
-
8909
collector << ", " unless i == 0
-
8909
collector = visit(x, collector)
-
end
-
end
-
-
34912
visit_Arel_Nodes_SelectOptions(o, collector)
-
end
-
-
3
def visit_Arel_Nodes_SelectOptions(o, collector)
-
34912
collector = maybe_visit o.limit, collector
-
34912
collector = maybe_visit o.offset, collector
-
34912
maybe_visit o.lock, collector
-
end
-
-
3
def visit_Arel_Nodes_SelectCore(o, collector)
-
34924
collector << "SELECT"
-
-
34924
collector = collect_optimizer_hints(o, collector)
-
34924
collector = maybe_visit o.set_quantifier, collector
-
-
34921
collect_nodes_for o.projections, collector, " "
-
-
34921
if o.source && !o.source.empty?
-
34876
collector << " FROM "
-
34876
collector = visit o.source, collector
-
end
-
-
34921
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
-
34921
collect_nodes_for o.groups, collector, " GROUP BY "
-
34921
collect_nodes_for o.havings, collector, " HAVING ", " AND "
-
34921
collect_nodes_for o.windows, collector, " WINDOW "
-
-
34921
maybe_visit o.comment, collector
-
end
-
-
3
def visit_Arel_Nodes_OptimizerHints(o, collector)
-
18
hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(" ")
-
9
collector << "/*+ #{hints} */"
-
end
-
-
3
def visit_Arel_Nodes_Comment(o, collector)
-
231
collector << o.values.map { |v| "/* #{sanitize_as_sql_comment(v)} */" }.join(" ")
-
end
-
-
3
def collect_nodes_for(nodes, collector, spacer, connector = ", ")
-
193868
unless nodes.empty?
-
72532
collector << spacer
-
72532
inject_join nodes, collector, connector
-
end
-
end
-
-
3
def visit_Arel_Nodes_Bin(o, collector)
-
3
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_Distinct(o, collector)
-
499
collector << "DISTINCT"
-
end
-
-
3
def visit_Arel_Nodes_DistinctOn(o, collector)
-
3
raise NotImplementedError, "DISTINCT ON not implemented for this db"
-
end
-
-
3
def visit_Arel_Nodes_With(o, collector)
-
6
collector << "WITH "
-
6
collect_ctes(o.children, collector)
-
end
-
-
3
def visit_Arel_Nodes_WithRecursive(o, collector)
-
6
collector << "WITH RECURSIVE "
-
6
collect_ctes(o.children, collector)
-
end
-
-
3
def visit_Arel_Nodes_Union(o, collector)
-
21
infix_value_with_paren(o, collector, " UNION ")
-
end
-
-
3
def visit_Arel_Nodes_UnionAll(o, collector)
-
9
infix_value_with_paren(o, collector, " UNION ALL ")
-
end
-
-
3
def visit_Arel_Nodes_Intersect(o, collector)
-
3
collector << "( "
-
3
infix_value(o, collector, " INTERSECT ") << " )"
-
end
-
-
3
def visit_Arel_Nodes_Except(o, collector)
-
3
collector << "( "
-
3
infix_value(o, collector, " EXCEPT ") << " )"
-
end
-
-
3
def visit_Arel_Nodes_NamedWindow(o, collector)
-
54
collector << quote_column_name(o.name)
-
54
collector << " AS "
-
54
visit_Arel_Nodes_Window o, collector
-
end
-
-
3
def visit_Arel_Nodes_Window(o, collector)
-
57
collector << "("
-
-
57
collect_nodes_for o.partitions, collector, "PARTITION BY "
-
-
57
if o.orders.any?
-
12
collector << " " if o.partitions.any?
-
12
collector << "ORDER BY "
-
12
collector = inject_join o.orders, collector, ", "
-
end
-
-
57
if o.framing
-
36
collector << " " if o.partitions.any? || o.orders.any?
-
36
collector = visit o.framing, collector
-
end
-
-
57
collector << ")"
-
end
-
-
3
def visit_Arel_Nodes_Rows(o, collector)
-
18
if o.expr
-
15
collector << "ROWS "
-
15
visit o.expr, collector
-
else
-
3
collector << "ROWS"
-
end
-
end
-
-
3
def visit_Arel_Nodes_Range(o, collector)
-
18
if o.expr
-
15
collector << "RANGE "
-
15
visit o.expr, collector
-
else
-
3
collector << "RANGE"
-
end
-
end
-
-
3
def visit_Arel_Nodes_Preceding(o, collector)
-
18
collector = if o.expr
-
6
visit o.expr, collector
-
else
-
12
collector << "UNBOUNDED"
-
end
-
-
18
collector << " PRECEDING"
-
end
-
-
3
def visit_Arel_Nodes_Following(o, collector)
-
12
collector = if o.expr
-
6
visit o.expr, collector
-
else
-
6
collector << "UNBOUNDED"
-
end
-
-
12
collector << " FOLLOWING"
-
end
-
-
3
def visit_Arel_Nodes_CurrentRow(o, collector)
-
12
collector << "CURRENT ROW"
-
end
-
-
3
def visit_Arel_Nodes_Over(o, collector)
-
15
case o.right
-
when nil
-
6
visit(o.left, collector) << " OVER ()"
-
when Arel::Nodes::SqlLiteral
-
3
infix_value o, collector, " OVER "
-
when String, Symbol
-
3
visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}"
-
else
-
3
infix_value o, collector, " OVER "
-
end
-
end
-
-
3
def visit_Arel_Nodes_Offset(o, collector)
-
393
collector << "OFFSET "
-
393
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_Limit(o, collector)
-
20147
collector << "LIMIT "
-
20147
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_Lock(o, collector)
-
34
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_Grouping(o, collector)
-
2209
if o.expr.is_a? Nodes::Grouping
-
3
visit(o.expr, collector)
-
else
-
2206
collector << "("
-
2206
visit(o.expr, collector) << ")"
-
end
-
end
-
-
3
def visit_Arel_Nodes_HomogeneousIn(o, collector)
-
3975
collector.preparable = false
-
-
3975
collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name)
-
-
3975
if o.type == :in
-
3942
collector << " IN ("
-
else
-
33
collector << " NOT IN ("
-
end
-
-
3975
values = o.casted_values
-
-
3975
if values.empty?
-
collector << @connection.quote(nil)
-
else
-
3975
collector.add_binds(values, &bind_block)
-
end
-
-
3975
collector << ")"
-
3975
collector
-
end
-
-
3
def visit_Arel_SelectManager(o, collector)
-
15
collector << "("
-
15
visit(o.ast, collector) << ")"
-
end
-
-
3
def visit_Arel_Nodes_Ascending(o, collector)
-
5745
visit(o.expr, collector) << " ASC"
-
end
-
-
3
def visit_Arel_Nodes_Descending(o, collector)
-
513
visit(o.expr, collector) << " DESC"
-
end
-
-
3
def visit_Arel_Nodes_Group(o, collector)
-
406
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_NamedFunction(o, collector)
-
1182
collector << o.name
-
1182
collector << "("
-
1182
collector << "DISTINCT " if o.distinct
-
1182
collector = inject_join(o.expressions, collector, ", ") << ")"
-
1182
if o.alias
-
collector << " AS "
-
visit o.alias, collector
-
else
-
1182
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_Extract(o, collector)
-
6
collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
-
6
visit(o.expr, collector) << ")"
-
end
-
-
3
def visit_Arel_Nodes_Count(o, collector)
-
4005
aggregate "COUNT", o, collector
-
end
-
-
3
def visit_Arel_Nodes_Sum(o, collector)
-
175
aggregate "SUM", o, collector
-
end
-
-
3
def visit_Arel_Nodes_Max(o, collector)
-
130
aggregate "MAX", o, collector
-
end
-
-
3
def visit_Arel_Nodes_Min(o, collector)
-
108
aggregate "MIN", o, collector
-
end
-
-
3
def visit_Arel_Nodes_Avg(o, collector)
-
78
aggregate "AVG", o, collector
-
end
-
-
3
def visit_Arel_Nodes_TableAlias(o, collector)
-
885
collector = visit o.relation, collector
-
885
collector << " "
-
885
collector << quote_table_name(o.name)
-
end
-
-
3
def visit_Arel_Nodes_Between(o, collector)
-
183
collector = visit o.left, collector
-
183
collector << " BETWEEN "
-
183
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_GreaterThanOrEqual(o, collector)
-
126
collector = visit o.left, collector
-
126
collector << " >= "
-
126
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_GreaterThan(o, collector)
-
837
collector = visit o.left, collector
-
837
collector << " > "
-
837
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_LessThanOrEqual(o, collector)
-
75
collector = visit o.left, collector
-
75
collector << " <= "
-
75
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_LessThan(o, collector)
-
147
collector = visit o.left, collector
-
147
collector << " < "
-
147
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_Matches(o, collector)
-
24
collector = visit o.left, collector
-
24
collector << " LIKE "
-
24
collector = visit o.right, collector
-
24
if o.escape
-
3
collector << " ESCAPE "
-
3
visit o.escape, collector
-
else
-
21
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_DoesNotMatch(o, collector)
-
24
collector = visit o.left, collector
-
24
collector << " NOT LIKE "
-
24
collector = visit o.right, collector
-
24
if o.escape
-
3
collector << " ESCAPE "
-
3
visit o.escape, collector
-
else
-
21
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_JoinSource(o, collector)
-
34921
if o.left
-
34909
collector = visit o.left, collector
-
end
-
34921
if o.right.any?
-
3910
collector << " " if o.left
-
3910
collector = inject_join o.right, collector, " "
-
end
-
34921
collector
-
end
-
-
3
def visit_Arel_Nodes_Regexp(o, collector)
-
3
raise NotImplementedError, "~ not implemented for this db"
-
end
-
-
3
def visit_Arel_Nodes_NotRegexp(o, collector)
-
3
raise NotImplementedError, "!~ not implemented for this db"
-
end
-
-
3
def visit_Arel_Nodes_StringJoin(o, collector)
-
102
visit o.left, collector
-
end
-
-
3
def visit_Arel_Nodes_FullOuterJoin(o, collector)
-
3
collector << "FULL OUTER JOIN "
-
3
collector = visit o.left, collector
-
3
collector << " "
-
3
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_OuterJoin(o, collector)
-
1813
collector << "LEFT OUTER JOIN "
-
1813
collector = visit o.left, collector
-
1813
collector << " "
-
1813
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_RightOuterJoin(o, collector)
-
3
collector << "RIGHT OUTER JOIN "
-
3
collector = visit o.left, collector
-
3
collector << " "
-
3
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_InnerJoin(o, collector)
-
3434
collector << "INNER JOIN "
-
3434
collector = visit o.left, collector
-
3434
if o.right
-
3431
collector << " "
-
3431
visit(o.right, collector)
-
else
-
3
collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_On(o, collector)
-
5244
collector << "ON "
-
5244
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_Not(o, collector)
-
15
collector << "NOT ("
-
15
visit(o.expr, collector) << ")"
-
end
-
-
3
def visit_Arel_Table(o, collector)
-
213145
if o.table_alias
-
21
collector << quote_table_name(o.name) << " " << quote_table_name(o.table_alias)
-
else
-
213124
collector << quote_table_name(o.name)
-
end
-
end
-
-
3
def visit_Arel_Nodes_In(o, collector)
-
393
collector.preparable = false
-
393
attr, values = o.left, o.right
-
-
393
if Array === values
-
276
unless values.empty?
-
441
values.delete_if { |value| unboundable?(value) }
-
end
-
-
276
return collector << "1=0" if values.empty?
-
end
-
-
324
visit(attr, collector) << " IN ("
-
324
visit(values, collector) << ")"
-
end
-
-
3
def visit_Arel_Nodes_NotIn(o, collector)
-
41
collector.preparable = false
-
41
attr, values = o.left, o.right
-
-
41
if Array === values
-
35
unless values.empty?
-
60
values.delete_if { |value| unboundable?(value) }
-
end
-
-
35
return collector << "1=1" if values.empty?
-
end
-
-
24
visit(attr, collector) << " NOT IN ("
-
24
visit(values, collector) << ")"
-
end
-
-
3
def visit_Arel_Nodes_And(o, collector)
-
12553
inject_join o.children, collector, " AND "
-
end
-
-
3
def visit_Arel_Nodes_Or(o, collector)
-
292
stack = [o.right, o.left]
-
-
292
while o = stack.pop
-
12634
if o.is_a?(Arel::Nodes::Or)
-
6025
stack.push o.right, o.left
-
else
-
6609
visit o, collector
-
6609
collector << " OR " unless stack.empty?
-
end
-
end
-
-
292
collector
-
end
-
-
3
def visit_Arel_Nodes_Assignment(o, collector)
-
6131
case o.right
-
when Arel::Nodes::Node, Arel::Attributes::Attribute
-
6110
collector = visit o.left, collector
-
6110
collector << " = "
-
6110
visit o.right, collector
-
else
-
21
collector = visit o.left, collector
-
21
collector << " = "
-
21
collector << quote(o.right).to_s
-
end
-
end
-
-
3
def visit_Arel_Nodes_Equality(o, collector)
-
51022
right = o.right
-
-
51022
return collector << "1=0" if unboundable?(right)
-
-
51010
collector = visit o.left, collector
-
-
51010
if right.nil?
-
361
collector << " IS NULL"
-
else
-
50649
collector << " = "
-
50649
visit right, collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
-
9
if o.right.nil?
-
3
collector = visit o.left, collector
-
3
collector << " IS NULL"
-
else
-
6
collector = is_distinct_from(o, collector)
-
6
collector << " = 0"
-
end
-
end
-
-
3
def visit_Arel_Nodes_IsDistinctFrom(o, collector)
-
6
if o.right.nil?
-
3
collector = visit o.left, collector
-
3
collector << " IS NOT NULL"
-
else
-
3
collector = is_distinct_from(o, collector)
-
3
collector << " = 1"
-
end
-
end
-
-
3
def visit_Arel_Nodes_NotEqual(o, collector)
-
205
right = o.right
-
-
205
return collector << "1=1" if unboundable?(right)
-
-
202
collector = visit o.left, collector
-
-
202
if right.nil?
-
33
collector << " IS NOT NULL"
-
else
-
169
collector << " != "
-
169
visit right, collector
-
end
-
end
-
-
3
def visit_Arel_Nodes_As(o, collector)
-
10820
collector = visit o.left, collector
-
10820
collector << " AS "
-
10820
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_Case(o, collector)
-
18
collector << "CASE "
-
18
if o.case
-
15
visit o.case, collector
-
15
collector << " "
-
end
-
18
o.conditions.each do |condition|
-
24
visit condition, collector
-
24
collector << " "
-
end
-
18
if o.default
-
12
visit o.default, collector
-
12
collector << " "
-
end
-
18
collector << "END"
-
end
-
-
3
def visit_Arel_Nodes_When(o, collector)
-
24
collector << "WHEN "
-
24
visit o.left, collector
-
24
collector << " THEN "
-
24
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_Else(o, collector)
-
12
collector << "ELSE "
-
12
visit o.expr, collector
-
end
-
-
3
def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
-
7183
collector << quote_column_name(o.name)
-
end
-
-
3
def visit_Arel_Attributes_Attribute(o, collector)
-
124534
join_name = o.relation.table_alias || o.relation.name
-
124534
collector << quote_table_name(join_name) << "." << quote_column_name(o.name)
-
end
-
-
338963
BIND_BLOCK = proc { "?" }
-
3
private_constant :BIND_BLOCK
-
-
63079
def bind_block; BIND_BLOCK; end
-
-
3
def visit_Arel_Nodes_BindParam(o, collector)
-
104372
collector.add_bind(o.value, &bind_block)
-
end
-
-
3
def visit_Arel_Nodes_SqlLiteral(o, collector)
-
334477
collector.preparable = false
-
334477
collector << o.to_s
-
end
-
-
3
def visit_Integer(o, collector)
-
1335
collector << o.to_s
-
end
-
-
3
def unsupported(o, collector)
-
3
raise UnsupportedVisitError.new(o)
-
end
-
-
3
alias :visit_ActiveSupport_Multibyte_Chars :unsupported
-
3
alias :visit_ActiveSupport_StringInquirer :unsupported
-
3
alias :visit_BigDecimal :unsupported
-
3
alias :visit_Class :unsupported
-
3
alias :visit_Date :unsupported
-
3
alias :visit_DateTime :unsupported
-
3
alias :visit_FalseClass :unsupported
-
3
alias :visit_Float :unsupported
-
3
alias :visit_Hash :unsupported
-
3
alias :visit_NilClass :unsupported
-
3
alias :visit_String :unsupported
-
3
alias :visit_Symbol :unsupported
-
3
alias :visit_Time :unsupported
-
3
alias :visit_TrueClass :unsupported
-
-
3
def visit_Arel_Nodes_InfixOperation(o, collector)
-
1241
collector = visit o.left, collector
-
1241
collector << " #{o.operator} "
-
1241
visit o.right, collector
-
end
-
-
3
def visit_Arel_Nodes_UnaryOperation(o, collector)
-
6
collector << " #{o.operator} "
-
6
visit o.expr, collector
-
end
-
-
3
def visit_Array(o, collector)
-
279
inject_join o, collector, ", "
-
end
-
3
alias :visit_Set :visit_Array
-
-
3
def quote(value)
-
700909
return value if Arel::Nodes::SqlLiteral === value
-
700909
@connection.quote value
-
end
-
-
3
def quote_table_name(name)
-
342575
return name if Arel::Nodes::SqlLiteral === name
-
342221
@connection.quote_table_name(name)
-
end
-
-
3
def quote_column_name(name)
-
552401
return name if Arel::Nodes::SqlLiteral === name
-
529743
@connection.quote_column_name(name)
-
end
-
-
3
def sanitize_as_sql_comment(value)
-
147
return value if Arel::Nodes::SqlLiteral === value
-
147
@connection.sanitize_as_sql_comment(value)
-
end
-
-
3
def collect_optimizer_hints(o, collector)
-
34924
maybe_visit o.optimizer_hints, collector
-
end
-
-
3
def maybe_visit(thing, collector)
-
382953
return collector unless thing
-
187111
collector << " "
-
187111
visit thing, collector
-
end
-
-
3
def inject_join(list, collector, join_str)
-
94964
list.each_with_index do |x, i|
-
143068
collector << join_str unless i == 0
-
143068
collector = visit(x, collector)
-
end
-
94964
collector
-
end
-
-
3
def unboundable?(value)
-
51503
value.respond_to?(:unboundable?) && value.unboundable?
-
end
-
-
3
def has_join_sources?(o)
-
9656
o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty?
-
end
-
-
3
def has_limit_or_offset_or_orders?(o)
-
6266
o.limit || o.offset || !o.orders.empty?
-
end
-
-
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
-
# on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
-
# an UPDATE statement, so in the MySQL visitor we redefine this to do that.
-
3
def prepare_update_statement(o)
-
7516
if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o))
-
186
stmt = o.clone
-
186
stmt.limit = nil
-
186
stmt.offset = nil
-
186
stmt.orders = []
-
186
stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
-
186
stmt.relation = o.relation.left if has_join_sources?(o)
-
186
stmt
-
else
-
7330
o
-
end
-
end
-
3
alias :prepare_delete_statement :prepare_update_statement
-
-
# FIXME: we should probably have a 2-pass visitor for this
-
3
def build_subselect(key, o)
-
186
stmt = Nodes::SelectStatement.new
-
186
core = stmt.cores.first
-
186
core.froms = o.relation
-
186
core.wheres = o.wheres
-
186
core.projections = [key]
-
186
stmt.limit = o.limit
-
186
stmt.offset = o.offset
-
186
stmt.orders = o.orders
-
186
stmt
-
end
-
-
3
def infix_value(o, collector, value)
-
75
collector = visit o.left, collector
-
75
collector << value
-
75
visit o.right, collector
-
end
-
-
3
def infix_value_with_paren(o, collector, value, suppress_parens = false)
-
42
collector << "( " unless suppress_parens
-
42
collector = if o.left.class == o.class
-
6
infix_value_with_paren(o.left, collector, value, true)
-
else
-
36
visit o.left, collector
-
end
-
42
collector << value
-
42
collector = if o.right.class == o.class
-
6
infix_value_with_paren(o.right, collector, value, true)
-
else
-
36
visit o.right, collector
-
end
-
42
collector << " )" unless suppress_parens
-
42
collector
-
end
-
-
3
def aggregate(name, o, collector)
-
4496
collector << "#{name}("
-
4496
if o.distinct
-
180
collector << "DISTINCT "
-
end
-
4496
collector = inject_join(o.expressions, collector, ", ") << ")"
-
4496
if o.alias
-
268
collector << " AS "
-
268
visit o.alias, collector
-
else
-
4228
collector
-
end
-
end
-
-
3
def is_distinct_from(o, collector)
-
9
collector << "CASE WHEN "
-
9
collector = visit o.left, collector
-
9
collector << " = "
-
9
collector = visit o.right, collector
-
9
collector << " OR ("
-
9
collector = visit o.left, collector
-
9
collector << " IS NULL AND "
-
9
collector = visit o.right, collector
-
9
collector << " IS NULL)"
-
9
collector << " THEN 0 ELSE 1 END"
-
end
-
-
3
def collect_ctes(children, collector)
-
12
children.each_with_index do |child, i|
-
15
collector << ", " unless i == 0
-
-
15
case child
-
when Arel::Nodes::As
-
6
name = child.left.name
-
6
relation = child.right
-
when Arel::Nodes::TableAlias
-
9
name = child.name
-
9
relation = child.relation
-
end
-
-
15
collector << quote_table_name(name)
-
15
collector << " AS "
-
15
visit relation, collector
-
end
-
-
12
collector
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module Visitors
-
3
class Visitor
-
3
def initialize
-
3491
@dispatch = get_dispatch_cache
-
end
-
-
3
def accept(object, collector = nil)
-
209407
visit object, collector
-
end
-
-
3
private
-
3
attr_reader :dispatch
-
-
3
def self.dispatch_cache
-
3491
@dispatch_cache ||= Hash.new do |hash, klass|
-
777
hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
-
end
-
end
-
-
3
def get_dispatch_cache
-
3491
self.class.dispatch_cache
-
end
-
-
3
def visit(object, collector = nil)
-
1330413
dispatch_method = dispatch[object.class]
-
1330413
if collector
-
1330095
send dispatch_method, object, collector
-
else
-
318
send dispatch_method, object
-
end
-
rescue NoMethodError => e
-
75
raise e if respond_to?(dispatch_method, true)
-
75
superklass = object.class.ancestors.find { |klass|
-
147
respond_to?(dispatch[klass], true)
-
}
-
75
raise(TypeError, "Cannot visit #{object.class}") unless superklass
-
75
dispatch[object.class] = dispatch[superklass]
-
75
retry
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module Arel # :nodoc: all
-
3
module WindowPredications
-
3
def over(expr = nil)
-
15
Nodes::Over.new(self, expr)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "rails/generators/named_base"
-
require "rails/generators/active_model"
-
require "rails/generators/active_record/migration"
-
require "active_record"
-
-
module ActiveRecord
-
module Generators # :nodoc:
-
class Base < Rails::Generators::NamedBase # :nodoc:
-
include ActiveRecord::Generators::Migration
-
-
# Set the current directory as base for the inherited generators.
-
def self.base_root
-
__dir__
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "rails/generators/active_record"
-
-
module ActiveRecord
-
module Generators # :nodoc:
-
class ApplicationRecordGenerator < ::Rails::Generators::Base # :nodoc:
-
source_root File.expand_path("templates", __dir__)
-
-
# FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
-
def create_application_record
-
template "application_record.rb", application_record_file_name
-
end
-
-
private
-
def application_record_file_name
-
@application_record_file_name ||=
-
if namespaced?
-
"app/models/#{namespaced_path}/application_record.rb"
-
else
-
"app/models/application_record.rb"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "rails/generators/migration"
-
-
module ActiveRecord
-
module Generators # :nodoc:
-
module Migration
-
extend ActiveSupport::Concern
-
include Rails::Generators::Migration
-
-
module ClassMethods
-
# Implement the required interface for Rails::Generators::Migration.
-
def next_migration_number(dirname)
-
next_migration_number = current_migration_number(dirname) + 1
-
ActiveRecord::Migration.next_migration_number(next_migration_number)
-
end
-
end
-
-
private
-
def primary_key_type
-
key_type = options[:primary_key_type]
-
", id: :#{key_type}" if key_type
-
end
-
-
def foreign_key_type
-
key_type = options[:primary_key_type]
-
", type: :#{key_type}" if key_type
-
end
-
-
def db_migrate_path
-
if defined?(Rails.application) && Rails.application
-
configured_migrate_path || default_migrate_path
-
else
-
"db/migrate"
-
end
-
end
-
-
def default_migrate_path
-
Rails.application.config.paths["db/migrate"].to_ary.first
-
end
-
-
def configured_migrate_path
-
return unless database = options[:database]
-
config = ActiveRecord::Base.configurations.configs_for(
-
env_name: Rails.env,
-
name: database
-
)
-
config&.migrations_paths
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "rails/generators/active_record"
-
-
module ActiveRecord
-
module Generators # :nodoc:
-
class MigrationGenerator < Base # :nodoc:
-
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
-
-
class_option :timestamps, type: :boolean
-
class_option :primary_key_type, type: :string, desc: "The type for primary key"
-
class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
-
-
def create_migration_file
-
set_local_assigns!
-
validate_file_name!
-
migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb")
-
end
-
-
private
-
attr_reader :migration_action, :join_tables
-
-
# Sets the default migration template that is being used for the generation of the migration.
-
# Depending on command line arguments, the migration template and the table name instance
-
# variables are set up.
-
def set_local_assigns!
-
@migration_template = "migration.rb"
-
case file_name
-
when /^(add)_.*_to_(.*)/, /^(remove)_.*?_from_(.*)/
-
@migration_action = $1
-
@table_name = normalize_table_name($2)
-
when /join_table/
-
if attributes.length == 2
-
@migration_action = "join"
-
@join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name)
-
-
set_index_names
-
end
-
when /^create_(.+)/
-
@table_name = normalize_table_name($1)
-
@migration_template = "create_table_migration.rb"
-
end
-
end
-
-
def set_index_names
-
attributes.each_with_index do |attr, i|
-
attr.index_name = [attr, attributes[i - 1]].map { |a| index_name_for(a) }
-
end
-
end
-
-
def index_name_for(attribute)
-
if attribute.foreign_key?
-
attribute.name
-
else
-
attribute.name.singularize.foreign_key
-
end.to_sym
-
end
-
-
def attributes_with_index
-
attributes.select { |a| !a.reference? && a.has_index? }
-
end
-
-
# A migration file name can only contain underscores (_), lowercase characters,
-
# and numbers 0-9. Any other file name will raise an IllegalMigrationNameError.
-
def validate_file_name!
-
unless /^[_a-z0-9]+$/.match?(file_name)
-
raise IllegalMigrationNameError.new(file_name)
-
end
-
end
-
-
def normalize_table_name(_table_name)
-
pluralize_table_names? ? _table_name.pluralize : _table_name.singularize
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "rails/generators/active_record"
-
-
module ActiveRecord
-
module Generators # :nodoc:
-
class ModelGenerator < Base # :nodoc:
-
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
-
-
check_class_collision
-
-
class_option :migration, type: :boolean
-
class_option :timestamps, type: :boolean
-
class_option :parent, type: :string, desc: "The parent class for the generated model"
-
class_option :indexes, type: :boolean, default: true, desc: "Add indexes for references and belongs_to columns"
-
class_option :primary_key_type, type: :string, desc: "The type for primary key"
-
class_option :database, type: :string, aliases: %i(--db), desc: "The database for your model's migration. By default, the current environment's primary database is used."
-
-
# creates the migration file for the model.
-
def create_migration_file
-
return if skip_migration_creation?
-
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
-
migration_template "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb")
-
end
-
-
def create_model_file
-
generate_abstract_class if database && !parent
-
template "model.rb", File.join("app/models", class_path, "#{file_name}.rb")
-
end
-
-
def create_module_file
-
return if regular_class_path.empty?
-
template "module.rb", File.join("app/models", "#{class_path.join('/')}.rb") if behavior == :invoke
-
end
-
-
hook_for :test_framework
-
-
private
-
# Skip creating migration file if:
-
# - options parent is present and database option is not present
-
# - migrations option is nil or false
-
def skip_migration_creation?
-
parent && !database || !migration
-
end
-
-
def attributes_with_index
-
attributes.select { |a| !a.reference? && a.has_index? }
-
end
-
-
# Used by the migration template to determine the parent name of the model
-
def parent_class_name
-
if parent
-
parent
-
elsif database
-
abstract_class_name
-
else
-
"ApplicationRecord"
-
end
-
end
-
-
def generate_abstract_class
-
path = File.join("app/models", "#{database.underscore}_record.rb")
-
return if File.exist?(path)
-
-
template "abstract_base_class.rb", path
-
end
-
-
def abstract_class_name
-
"#{database.camelize}Record"
-
end
-
-
def database
-
options[:database]
-
end
-
-
def parent
-
options[:parent]
-
end
-
-
def migration
-
options[:migration]
-
end
-
end
-
end
-
end